netwerk/cookie/nsCookieService.cpp

changeset 0
6474c204b198
child 4
fc2d59ddac77
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/cookie/nsCookieService.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,4369 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set sw=2 ts=8 et tw=80 : */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/Attributes.h"
    1.11 +#include "mozilla/DebugOnly.h"
    1.12 +#include "mozilla/Likely.h"
    1.13 +
    1.14 +#ifdef MOZ_LOGGING
    1.15 +// this next define has to appear before the include of prlog.h
    1.16 +#define FORCE_PR_LOG // Allow logging in the release build
    1.17 +#endif
    1.18 +
    1.19 +#include "mozilla/net/CookieServiceChild.h"
    1.20 +#include "mozilla/net/NeckoCommon.h"
    1.21 +
    1.22 +#include "nsCookieService.h"
    1.23 +#include "nsIServiceManager.h"
    1.24 +
    1.25 +#include "nsIIOService.h"
    1.26 +#include "nsIPrefBranch.h"
    1.27 +#include "nsIPrefService.h"
    1.28 +#include "nsICookiePermission.h"
    1.29 +#include "nsIURI.h"
    1.30 +#include "nsIURL.h"
    1.31 +#include "nsIChannel.h"
    1.32 +#include "nsIFile.h"
    1.33 +#include "nsIObserverService.h"
    1.34 +#include "nsILineInputStream.h"
    1.35 +#include "nsIEffectiveTLDService.h"
    1.36 +#include "nsIIDNService.h"
    1.37 +#include "mozIThirdPartyUtil.h"
    1.38 +
    1.39 +#include "nsTArray.h"
    1.40 +#include "nsCOMArray.h"
    1.41 +#include "nsIMutableArray.h"
    1.42 +#include "nsArrayEnumerator.h"
    1.43 +#include "nsEnumeratorUtils.h"
    1.44 +#include "nsAutoPtr.h"
    1.45 +#include "nsReadableUtils.h"
    1.46 +#include "nsCRT.h"
    1.47 +#include "prprf.h"
    1.48 +#include "nsNetUtil.h"
    1.49 +#include "nsNetCID.h"
    1.50 +#include "nsAppDirectoryServiceDefs.h"
    1.51 +#include "nsNetCID.h"
    1.52 +#include "mozilla/storage.h"
    1.53 +#include "mozilla/AutoRestore.h"
    1.54 +#include "mozilla/FileUtils.h"
    1.55 +#include "mozilla/Telemetry.h"
    1.56 +#include "nsIAppsService.h"
    1.57 +#include "mozIApplication.h"
    1.58 +#include "nsIConsoleService.h"
    1.59 +
    1.60 +using namespace mozilla;
    1.61 +using namespace mozilla::net;
    1.62 +
    1.63 +// Create key from baseDomain that will access the default cookie namespace.
    1.64 +// TODO: When we figure out what the API will look like for nsICookieManager{2}
    1.65 +// on content processes (see bug 777620), change to use the appropriate app
    1.66 +// namespace.  For now those IDLs aren't supported on child processes.
    1.67 +#define DEFAULT_APP_KEY(baseDomain) \
    1.68 +        nsCookieKey(baseDomain, NECKO_NO_APP_ID, false)
    1.69 +
    1.70 +/******************************************************************************
    1.71 + * nsCookieService impl:
    1.72 + * useful types & constants
    1.73 + ******************************************************************************/
    1.74 +
    1.75 +static nsCookieService *gCookieService;
    1.76 +
    1.77 +// XXX_hack. See bug 178993.
    1.78 +// This is a hack to hide HttpOnly cookies from older browsers
    1.79 +static const char kHttpOnlyPrefix[] = "#HttpOnly_";
    1.80 +
    1.81 +#define COOKIES_FILE "cookies.sqlite"
    1.82 +#define COOKIES_SCHEMA_VERSION 5
    1.83 +
    1.84 +// parameter indexes; see EnsureReadDomain, EnsureReadComplete and
    1.85 +// ReadCookieDBListener::HandleResult
    1.86 +#define IDX_NAME 0
    1.87 +#define IDX_VALUE 1
    1.88 +#define IDX_HOST 2
    1.89 +#define IDX_PATH 3
    1.90 +#define IDX_EXPIRY 4
    1.91 +#define IDX_LAST_ACCESSED 5
    1.92 +#define IDX_CREATION_TIME 6
    1.93 +#define IDX_SECURE 7
    1.94 +#define IDX_HTTPONLY 8
    1.95 +#define IDX_BASE_DOMAIN 9
    1.96 +#define IDX_APP_ID 10
    1.97 +#define IDX_BROWSER_ELEM 11
    1.98 +
    1.99 +static const int64_t kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
   1.100 +static const int64_t kCookiePurgeAge =
   1.101 +  int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
   1.102 +
   1.103 +static const char kOldCookieFileName[] = "cookies.txt";
   1.104 +
   1.105 +#undef  LIMIT
   1.106 +#define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
   1.107 +
   1.108 +#undef  ADD_TEN_PERCENT
   1.109 +#define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
   1.110 +
   1.111 +// default limits for the cookie list. these can be tuned by the
   1.112 +// network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
   1.113 +static const uint32_t kMaxNumberOfCookies = 3000;
   1.114 +static const uint32_t kMaxCookiesPerHost  = 150;
   1.115 +static const uint32_t kMaxBytesPerCookie  = 4096;
   1.116 +static const uint32_t kMaxBytesPerPath    = 1024;
   1.117 +
   1.118 +// behavior pref constants
   1.119 +static const uint32_t BEHAVIOR_ACCEPT        = 0; // allow all cookies
   1.120 +static const uint32_t BEHAVIOR_REJECTFOREIGN = 1; // reject all third-party cookies
   1.121 +static const uint32_t BEHAVIOR_REJECT        = 2; // reject all cookies
   1.122 +static const uint32_t BEHAVIOR_LIMITFOREIGN  = 3; // reject third-party cookies unless the
   1.123 +                                                  // eTLD already has at least one cookie
   1.124 +
   1.125 +// pref string constants
   1.126 +static const char kPrefCookieBehavior[]     = "network.cookie.cookieBehavior";
   1.127 +static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
   1.128 +static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
   1.129 +static const char kPrefCookiePurgeAge[]     = "network.cookie.purgeAge";
   1.130 +static const char kPrefThirdPartySession[]  = "network.cookie.thirdparty.sessionOnly";
   1.131 +
   1.132 +static void
   1.133 +bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
   1.134 +                     const nsCookieKey &aKey,
   1.135 +                     const nsCookie *aCookie);
   1.136 +
   1.137 +// struct for temporarily storing cookie attributes during header parsing
   1.138 +struct nsCookieAttributes
   1.139 +{
   1.140 +  nsAutoCString name;
   1.141 +  nsAutoCString value;
   1.142 +  nsAutoCString host;
   1.143 +  nsAutoCString path;
   1.144 +  nsAutoCString expires;
   1.145 +  nsAutoCString maxage;
   1.146 +  int64_t expiryTime;
   1.147 +  bool isSession;
   1.148 +  bool isSecure;
   1.149 +  bool isHttpOnly;
   1.150 +};
   1.151 +
   1.152 +// stores the nsCookieEntry entryclass and an index into the cookie array
   1.153 +// within that entryclass, for purposes of storing an iteration state that
   1.154 +// points to a certain cookie.
   1.155 +struct nsListIter
   1.156 +{
   1.157 +  // default (non-initializing) constructor.
   1.158 +  nsListIter()
   1.159 +  {
   1.160 +  }
   1.161 +
   1.162 +  // explicit constructor to a given iterator state with entryclass 'aEntry'
   1.163 +  // and index 'aIndex'.
   1.164 +  explicit
   1.165 +  nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
   1.166 +   : entry(aEntry)
   1.167 +   , index(aIndex)
   1.168 +  {
   1.169 +  }
   1.170 +
   1.171 +  // get the nsCookie * the iterator currently points to.
   1.172 +  nsCookie * Cookie() const
   1.173 +  {
   1.174 +    return entry->GetCookies()[index];
   1.175 +  }
   1.176 +
   1.177 +  nsCookieEntry            *entry;
   1.178 +  nsCookieEntry::IndexType  index;
   1.179 +};
   1.180 +
   1.181 +/******************************************************************************
   1.182 + * Cookie logging handlers
   1.183 + * used for logging in nsCookieService
   1.184 + ******************************************************************************/
   1.185 +
   1.186 +// logging handlers
   1.187 +#ifdef MOZ_LOGGING
   1.188 +// in order to do logging, the following environment variables need to be set:
   1.189 +//
   1.190 +//    set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
   1.191 +//    set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
   1.192 +//    set NSPR_LOG_FILE=cookie.log
   1.193 +//
   1.194 +#include "prlog.h"
   1.195 +#endif
   1.196 +
   1.197 +// define logging macros for convenience
   1.198 +#define SET_COOKIE true
   1.199 +#define GET_COOKIE false
   1.200 +
   1.201 +#ifdef PR_LOGGING
   1.202 +static PRLogModuleInfo *
   1.203 +GetCookieLog()
   1.204 +{
   1.205 +  static PRLogModuleInfo *sCookieLog;
   1.206 +  if (!sCookieLog)
   1.207 +    sCookieLog = PR_NewLogModule("cookie");
   1.208 +  return sCookieLog;
   1.209 +}
   1.210 +
   1.211 +#define COOKIE_LOGFAILURE(a, b, c, d)    LogFailure(a, b, c, d)
   1.212 +#define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
   1.213 +
   1.214 +#define COOKIE_LOGEVICTED(a, details)          \
   1.215 +  PR_BEGIN_MACRO                               \
   1.216 +  if (PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG))  \
   1.217 +      LogEvicted(a, details);                  \
   1.218 +  PR_END_MACRO
   1.219 +
   1.220 +#define COOKIE_LOGSTRING(lvl, fmt)   \
   1.221 +  PR_BEGIN_MACRO                     \
   1.222 +    PR_LOG(GetCookieLog(), lvl, fmt);  \
   1.223 +    PR_LOG(GetCookieLog(), lvl, ("\n")); \
   1.224 +  PR_END_MACRO
   1.225 +
   1.226 +static void
   1.227 +LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
   1.228 +{
   1.229 +  // if logging isn't enabled, return now to save cycles
   1.230 +  if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_WARNING))
   1.231 +    return;
   1.232 +
   1.233 +  nsAutoCString spec;
   1.234 +  if (aHostURI)
   1.235 +    aHostURI->GetAsciiSpec(spec);
   1.236 +
   1.237 +  PR_LOG(GetCookieLog(), PR_LOG_WARNING,
   1.238 +    ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
   1.239 +  PR_LOG(GetCookieLog(), PR_LOG_WARNING,("request URL: %s\n", spec.get()));
   1.240 +  if (aSetCookie)
   1.241 +    PR_LOG(GetCookieLog(), PR_LOG_WARNING,("cookie string: %s\n", aCookieString));
   1.242 +
   1.243 +  PRExplodedTime explodedTime;
   1.244 +  PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
   1.245 +  char timeString[40];
   1.246 +  PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   1.247 +
   1.248 +  PR_LOG(GetCookieLog(), PR_LOG_WARNING,("current time: %s", timeString));
   1.249 +  PR_LOG(GetCookieLog(), PR_LOG_WARNING,("rejected because %s\n", aReason));
   1.250 +  PR_LOG(GetCookieLog(), PR_LOG_WARNING,("\n"));
   1.251 +}
   1.252 +
   1.253 +static void
   1.254 +LogCookie(nsCookie *aCookie)
   1.255 +{
   1.256 +  PRExplodedTime explodedTime;
   1.257 +  PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
   1.258 +  char timeString[40];
   1.259 +  PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   1.260 +
   1.261 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("current time: %s", timeString));
   1.262 +
   1.263 +  if (aCookie) {
   1.264 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("----------------\n"));
   1.265 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get()));
   1.266 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get()));
   1.267 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
   1.268 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get()));
   1.269 +
   1.270 +    PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
   1.271 +                   PR_GMTParameters, &explodedTime);
   1.272 +    PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   1.273 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   1.274 +      ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
   1.275 +
   1.276 +    PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
   1.277 +    PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   1.278 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("created: %s", timeString));
   1.279 +
   1.280 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
   1.281 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
   1.282 +  }
   1.283 +}
   1.284 +
   1.285 +static void
   1.286 +LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
   1.287 +{
   1.288 +  // if logging isn't enabled, return now to save cycles
   1.289 +  if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG)) {
   1.290 +    return;
   1.291 +  }
   1.292 +
   1.293 +  nsAutoCString spec;
   1.294 +  if (aHostURI)
   1.295 +    aHostURI->GetAsciiSpec(spec);
   1.296 +
   1.297 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   1.298 +    ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
   1.299 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
   1.300 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
   1.301 +  if (aSetCookie)
   1.302 +    PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
   1.303 +
   1.304 +  LogCookie(aCookie);
   1.305 +
   1.306 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   1.307 +}
   1.308 +
   1.309 +static void
   1.310 +LogEvicted(nsCookie *aCookie, const char* details)
   1.311 +{
   1.312 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
   1.313 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s\n", details));
   1.314 +
   1.315 +  LogCookie(aCookie);
   1.316 +
   1.317 +  PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   1.318 +}
   1.319 +
   1.320 +// inline wrappers to make passing in nsAFlatCStrings easier
   1.321 +static inline void
   1.322 +LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
   1.323 +{
   1.324 +  LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
   1.325 +}
   1.326 +
   1.327 +static inline void
   1.328 +LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
   1.329 +{
   1.330 +  LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
   1.331 +}
   1.332 +
   1.333 +#else
   1.334 +#define COOKIE_LOGFAILURE(a, b, c, d)    PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   1.335 +#define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   1.336 +#define COOKIE_LOGEVICTED(a, b)          PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   1.337 +#define COOKIE_LOGSTRING(a, b)           PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   1.338 +#endif
   1.339 +
   1.340 +#ifdef DEBUG
   1.341 +#define NS_ASSERT_SUCCESS(res)                                               \
   1.342 +  PR_BEGIN_MACRO                                                             \
   1.343 +  nsresult __rv = res; /* Do not evaluate |res| more than once! */           \
   1.344 +  if (NS_FAILED(__rv)) {                                                     \
   1.345 +    char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
   1.346 +                            #res, __rv);                                     \
   1.347 +    NS_ASSERTION(NS_SUCCEEDED(__rv), msg);                                   \
   1.348 +    PR_smprintf_free(msg);                                                   \
   1.349 +  }                                                                          \
   1.350 +  PR_END_MACRO
   1.351 +#else
   1.352 +#define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   1.353 +#endif
   1.354 +
   1.355 +/******************************************************************************
   1.356 + * DBListenerErrorHandler impl:
   1.357 + * Parent class for our async storage listeners that handles the logging of
   1.358 + * errors.
   1.359 + ******************************************************************************/
   1.360 +class DBListenerErrorHandler : public mozIStorageStatementCallback
   1.361 +{
   1.362 +protected:
   1.363 +  DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
   1.364 +  nsRefPtr<DBState> mDBState;
   1.365 +  virtual const char *GetOpType() = 0;
   1.366 +
   1.367 +public:
   1.368 +  NS_IMETHOD HandleError(mozIStorageError* aError)
   1.369 +  {
   1.370 +    int32_t result = -1;
   1.371 +    aError->GetResult(&result);
   1.372 +
   1.373 +#ifdef PR_LOGGING
   1.374 +    nsAutoCString message;
   1.375 +    aError->GetMessage(message);
   1.376 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
   1.377 +      ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
   1.378 +       "performing operation '%s' with message '%s'; rebuilding database.",
   1.379 +       result, GetOpType(), message.get()));
   1.380 +#endif
   1.381 +
   1.382 +    // Rebuild the database.
   1.383 +    gCookieService->HandleCorruptDB(mDBState);
   1.384 +
   1.385 +    return NS_OK;
   1.386 +  }
   1.387 +};
   1.388 +
   1.389 +/******************************************************************************
   1.390 + * InsertCookieDBListener impl:
   1.391 + * mozIStorageStatementCallback used to track asynchronous insertion operations.
   1.392 + ******************************************************************************/
   1.393 +class InsertCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   1.394 +{
   1.395 +protected:
   1.396 +  virtual const char *GetOpType() { return "INSERT"; }
   1.397 +
   1.398 +public:
   1.399 +  NS_DECL_ISUPPORTS
   1.400 +
   1.401 +  InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   1.402 +  NS_IMETHOD HandleResult(mozIStorageResultSet*)
   1.403 +  {
   1.404 +    NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
   1.405 +    return NS_OK;
   1.406 +  }
   1.407 +  NS_IMETHOD HandleCompletion(uint16_t aReason)
   1.408 +  {
   1.409 +    // If we were rebuilding the db and we succeeded, make our corruptFlag say
   1.410 +    // so.
   1.411 +    if (mDBState->corruptFlag == DBState::REBUILDING &&
   1.412 +        aReason == mozIStorageStatementCallback::REASON_FINISHED) {
   1.413 +      COOKIE_LOGSTRING(PR_LOG_DEBUG,
   1.414 +        ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
   1.415 +      mDBState->corruptFlag = DBState::OK;
   1.416 +    }
   1.417 +    return NS_OK;
   1.418 +  }
   1.419 +};
   1.420 +
   1.421 +NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
   1.422 +
   1.423 +/******************************************************************************
   1.424 + * UpdateCookieDBListener impl:
   1.425 + * mozIStorageStatementCallback used to track asynchronous update operations.
   1.426 + ******************************************************************************/
   1.427 +class UpdateCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   1.428 +{
   1.429 +protected:
   1.430 +  virtual const char *GetOpType() { return "UPDATE"; }
   1.431 +
   1.432 +public:
   1.433 +  NS_DECL_ISUPPORTS
   1.434 +
   1.435 +  UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   1.436 +  NS_IMETHOD HandleResult(mozIStorageResultSet*)
   1.437 +  {
   1.438 +    NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
   1.439 +    return NS_OK;
   1.440 +  }
   1.441 +  NS_IMETHOD HandleCompletion(uint16_t aReason)
   1.442 +  {
   1.443 +    return NS_OK;
   1.444 +  }
   1.445 +};
   1.446 +
   1.447 +NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
   1.448 +
   1.449 +/******************************************************************************
   1.450 + * RemoveCookieDBListener impl:
   1.451 + * mozIStorageStatementCallback used to track asynchronous removal operations.
   1.452 + ******************************************************************************/
   1.453 +class RemoveCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   1.454 +{
   1.455 +protected:
   1.456 +  virtual const char *GetOpType() { return "REMOVE"; }
   1.457 +
   1.458 +public:
   1.459 +  NS_DECL_ISUPPORTS
   1.460 +
   1.461 +  RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   1.462 +  NS_IMETHOD HandleResult(mozIStorageResultSet*)
   1.463 +  {
   1.464 +    NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
   1.465 +    return NS_OK;
   1.466 +  }
   1.467 +  NS_IMETHOD HandleCompletion(uint16_t aReason)
   1.468 +  {
   1.469 +    return NS_OK;
   1.470 +  }
   1.471 +};
   1.472 +
   1.473 +NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
   1.474 +
   1.475 +/******************************************************************************
   1.476 + * ReadCookieDBListener impl:
   1.477 + * mozIStorageStatementCallback used to track asynchronous removal operations.
   1.478 + ******************************************************************************/
   1.479 +class ReadCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   1.480 +{
   1.481 +protected:
   1.482 +  virtual const char *GetOpType() { return "READ"; }
   1.483 +  bool mCanceled;
   1.484 +
   1.485 +public:
   1.486 +  NS_DECL_ISUPPORTS
   1.487 +
   1.488 +  ReadCookieDBListener(DBState* dbState)
   1.489 +    : DBListenerErrorHandler(dbState)
   1.490 +    , mCanceled(false)
   1.491 +  {
   1.492 +  }
   1.493 +
   1.494 +  void Cancel() { mCanceled = true; }
   1.495 +
   1.496 +  NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
   1.497 +  {
   1.498 +    nsCOMPtr<mozIStorageRow> row;
   1.499 +
   1.500 +    while (1) {
   1.501 +      DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
   1.502 +      NS_ASSERT_SUCCESS(rv);
   1.503 +
   1.504 +      if (!row)
   1.505 +        break;
   1.506 +
   1.507 +      CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
   1.508 +      row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
   1.509 +      tuple->key.mAppId = static_cast<uint32_t>(row->AsInt32(IDX_APP_ID));
   1.510 +      tuple->key.mInBrowserElement = static_cast<bool>(row->AsInt32(IDX_BROWSER_ELEM));
   1.511 +      tuple->cookie = gCookieService->GetCookieFromRow(row);
   1.512 +    }
   1.513 +
   1.514 +    return NS_OK;
   1.515 +  }
   1.516 +  NS_IMETHOD HandleCompletion(uint16_t aReason)
   1.517 +  {
   1.518 +    // Process the completion of the read operation. If we have been canceled,
   1.519 +    // we cannot assume that the cookieservice still has an open connection
   1.520 +    // or that it even refers to the same database, so we must return early.
   1.521 +    // Conversely, the cookieservice guarantees that if we have not been
   1.522 +    // canceled, the database connection is still alive and we can safely
   1.523 +    // operate on it.
   1.524 +
   1.525 +    if (mCanceled) {
   1.526 +      // We may receive a REASON_FINISHED after being canceled;
   1.527 +      // tweak the reason accordingly.
   1.528 +      aReason = mozIStorageStatementCallback::REASON_CANCELED;
   1.529 +    }
   1.530 +
   1.531 +    switch (aReason) {
   1.532 +    case mozIStorageStatementCallback::REASON_FINISHED:
   1.533 +      gCookieService->AsyncReadComplete();
   1.534 +      break;
   1.535 +    case mozIStorageStatementCallback::REASON_CANCELED:
   1.536 +      // Nothing more to do here. The partially read data has already been
   1.537 +      // thrown away.
   1.538 +      COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled"));
   1.539 +      break;
   1.540 +    case mozIStorageStatementCallback::REASON_ERROR:
   1.541 +      // Nothing more to do here. DBListenerErrorHandler::HandleError()
   1.542 +      // can handle it.
   1.543 +      COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error"));
   1.544 +      break;
   1.545 +    default:
   1.546 +      NS_NOTREACHED("invalid reason");
   1.547 +    }
   1.548 +    return NS_OK;
   1.549 +  }
   1.550 +};
   1.551 +
   1.552 +NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
   1.553 +
   1.554 +/******************************************************************************
   1.555 + * CloseCookieDBListener imp:
   1.556 + * Static mozIStorageCompletionCallback used to notify when the database is
   1.557 + * successfully closed.
   1.558 + ******************************************************************************/
   1.559 +class CloseCookieDBListener MOZ_FINAL :  public mozIStorageCompletionCallback
   1.560 +{
   1.561 +public:
   1.562 +  CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
   1.563 +  nsRefPtr<DBState> mDBState;
   1.564 +  NS_DECL_ISUPPORTS
   1.565 +
   1.566 +  NS_IMETHOD Complete(nsresult, nsISupports*)
   1.567 +  {
   1.568 +    gCookieService->HandleDBClosed(mDBState);
   1.569 +    return NS_OK;
   1.570 +  }
   1.571 +};
   1.572 +
   1.573 +NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
   1.574 +
   1.575 +namespace {
   1.576 +
   1.577 +class AppClearDataObserver MOZ_FINAL : public nsIObserver {
   1.578 +public:
   1.579 +  NS_DECL_ISUPPORTS
   1.580 +
   1.581 +  // nsIObserver implementation.
   1.582 +  NS_IMETHODIMP
   1.583 +  Observe(nsISupports *aSubject, const char *aTopic, const char16_t *data)
   1.584 +  {
   1.585 +    MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA));
   1.586 +
   1.587 +    uint32_t appId = NECKO_UNKNOWN_APP_ID;
   1.588 +    bool browserOnly = false;
   1.589 +    nsresult rv = NS_GetAppInfoFromClearDataNotification(aSubject, &appId,
   1.590 +                                                         &browserOnly);
   1.591 +    NS_ENSURE_SUCCESS(rv, rv);
   1.592 +
   1.593 +    nsCOMPtr<nsICookieManager2> cookieManager
   1.594 +      = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
   1.595 +    MOZ_ASSERT(cookieManager);
   1.596 +    return cookieManager->RemoveCookiesForApp(appId, browserOnly);
   1.597 +  }
   1.598 +};
   1.599 +
   1.600 +NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
   1.601 +
   1.602 +} // anonymous namespace
   1.603 +
   1.604 +size_t
   1.605 +nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   1.606 +{
   1.607 +  return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   1.608 +}
   1.609 +
   1.610 +size_t
   1.611 +nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   1.612 +{
   1.613 +  size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
   1.614 +
   1.615 +  amount += mCookies.SizeOfExcludingThis(aMallocSizeOf);
   1.616 +  for (uint32_t i = 0; i < mCookies.Length(); ++i) {
   1.617 +    amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
   1.618 +  }
   1.619 +
   1.620 +  return amount;
   1.621 +}
   1.622 +
   1.623 +static size_t
   1.624 +HostTableEntrySizeOfExcludingThis(nsCookieEntry *aEntry,
   1.625 +                                  MallocSizeOf aMallocSizeOf,
   1.626 +                                  void *arg)
   1.627 +{
   1.628 +  return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   1.629 +}
   1.630 +
   1.631 +size_t
   1.632 +CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   1.633 +{
   1.634 +  size_t amount = 0;
   1.635 +
   1.636 +  amount += key.SizeOfExcludingThis(aMallocSizeOf);
   1.637 +  amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
   1.638 +
   1.639 +  return amount;
   1.640 +}
   1.641 +
   1.642 +static size_t
   1.643 +ReadSetEntrySizeOfExcludingThis(nsCookieKey *aEntry,
   1.644 +                                MallocSizeOf aMallocSizeOf,
   1.645 +                                void *)
   1.646 +{
   1.647 +  return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   1.648 +}
   1.649 +
   1.650 +size_t
   1.651 +DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   1.652 +{
   1.653 +  size_t amount = 0;
   1.654 +
   1.655 +  amount += aMallocSizeOf(this);
   1.656 +  amount += hostTable.SizeOfExcludingThis(HostTableEntrySizeOfExcludingThis,
   1.657 +                                          aMallocSizeOf);
   1.658 +  amount += hostArray.SizeOfExcludingThis(aMallocSizeOf);
   1.659 +  for (uint32_t i = 0; i < hostArray.Length(); ++i) {
   1.660 +    amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
   1.661 +  }
   1.662 +  amount += readSet.SizeOfExcludingThis(ReadSetEntrySizeOfExcludingThis,
   1.663 +                                        aMallocSizeOf);
   1.664 +
   1.665 +  return amount;
   1.666 +}
   1.667 +
   1.668 +/******************************************************************************
   1.669 + * nsCookieService impl:
   1.670 + * singleton instance ctor/dtor methods
   1.671 + ******************************************************************************/
   1.672 +
   1.673 +nsICookieService*
   1.674 +nsCookieService::GetXPCOMSingleton()
   1.675 +{
   1.676 +  if (IsNeckoChild())
   1.677 +    return CookieServiceChild::GetSingleton();
   1.678 +
   1.679 +  return GetSingleton();
   1.680 +}
   1.681 +
   1.682 +nsCookieService*
   1.683 +nsCookieService::GetSingleton()
   1.684 +{
   1.685 +  NS_ASSERTION(!IsNeckoChild(), "not a parent process");
   1.686 +
   1.687 +  if (gCookieService) {
   1.688 +    NS_ADDREF(gCookieService);
   1.689 +    return gCookieService;
   1.690 +  }
   1.691 +
   1.692 +  // Create a new singleton nsCookieService.
   1.693 +  // We AddRef only once since XPCOM has rules about the ordering of module
   1.694 +  // teardowns - by the time our module destructor is called, it's too late to
   1.695 +  // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
   1.696 +  // cycles have already been completed and would result in serious leaks.
   1.697 +  // See bug 209571.
   1.698 +  gCookieService = new nsCookieService();
   1.699 +  if (gCookieService) {
   1.700 +    NS_ADDREF(gCookieService);
   1.701 +    if (NS_FAILED(gCookieService->Init())) {
   1.702 +      NS_RELEASE(gCookieService);
   1.703 +    }
   1.704 +  }
   1.705 +
   1.706 +  return gCookieService;
   1.707 +}
   1.708 +
   1.709 +/* static */ void
   1.710 +nsCookieService::AppClearDataObserverInit()
   1.711 +{
   1.712 +  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   1.713 +  nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
   1.714 +  observerService->AddObserver(obs, TOPIC_WEB_APP_CLEAR_DATA,
   1.715 +                               /* holdsWeak= */ false);
   1.716 +}
   1.717 +
   1.718 +/******************************************************************************
   1.719 + * nsCookieService impl:
   1.720 + * public methods
   1.721 + ******************************************************************************/
   1.722 +
   1.723 +NS_IMPL_ISUPPORTS(nsCookieService,
   1.724 +                  nsICookieService,
   1.725 +                  nsICookieManager,
   1.726 +                  nsICookieManager2,
   1.727 +                  nsIObserver,
   1.728 +                  nsISupportsWeakReference,
   1.729 +                  nsIMemoryReporter)
   1.730 +
   1.731 +nsCookieService::nsCookieService()
   1.732 + : mDBState(nullptr)
   1.733 + , mCookieBehavior(BEHAVIOR_ACCEPT)
   1.734 + , mThirdPartySession(false)
   1.735 + , mMaxNumberOfCookies(kMaxNumberOfCookies)
   1.736 + , mMaxCookiesPerHost(kMaxCookiesPerHost)
   1.737 + , mCookiePurgeAge(kCookiePurgeAge)
   1.738 +{
   1.739 +}
   1.740 +
   1.741 +nsresult
   1.742 +nsCookieService::Init()
   1.743 +{
   1.744 +  nsresult rv;
   1.745 +  mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
   1.746 +  NS_ENSURE_SUCCESS(rv, rv);
   1.747 +
   1.748 +  mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
   1.749 +  NS_ENSURE_SUCCESS(rv, rv);
   1.750 +
   1.751 +  mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   1.752 +  NS_ENSURE_SUCCESS(rv, rv);
   1.753 +
   1.754 +  // init our pref and observer
   1.755 +  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   1.756 +  if (prefBranch) {
   1.757 +    prefBranch->AddObserver(kPrefCookieBehavior,     this, true);
   1.758 +    prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
   1.759 +    prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, true);
   1.760 +    prefBranch->AddObserver(kPrefCookiePurgeAge,     this, true);
   1.761 +    prefBranch->AddObserver(kPrefThirdPartySession,  this, true);
   1.762 +    PrefChanged(prefBranch);
   1.763 +  }
   1.764 +
   1.765 +  mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
   1.766 +  NS_ENSURE_SUCCESS(rv, rv);
   1.767 +
   1.768 +  // Init our default, and possibly private DBStates.
   1.769 +  InitDBStates();
   1.770 +
   1.771 +  RegisterWeakMemoryReporter(this);
   1.772 +
   1.773 +  mObserverService = mozilla::services::GetObserverService();
   1.774 +  NS_ENSURE_STATE(mObserverService);
   1.775 +  mObserverService->AddObserver(this, "profile-before-change", true);
   1.776 +  mObserverService->AddObserver(this, "profile-do-change", true);
   1.777 +  mObserverService->AddObserver(this, "last-pb-context-exited", true);
   1.778 +
   1.779 +  mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
   1.780 +  if (!mPermissionService) {
   1.781 +    NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
   1.782 +    COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
   1.783 +  }
   1.784 +
   1.785 +  return NS_OK;
   1.786 +}
   1.787 +
   1.788 +void
   1.789 +nsCookieService::InitDBStates()
   1.790 +{
   1.791 +  NS_ASSERTION(!mDBState, "already have a DBState");
   1.792 +  NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
   1.793 +  NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
   1.794 +
   1.795 +  // Create a new default DBState and set our current one.
   1.796 +  mDefaultDBState = new DBState();
   1.797 +  mDBState = mDefaultDBState;
   1.798 +
   1.799 +  mPrivateDBState = new DBState();
   1.800 +
   1.801 +  // Get our cookie file.
   1.802 +  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   1.803 +    getter_AddRefs(mDefaultDBState->cookieFile));
   1.804 +  if (NS_FAILED(rv)) {
   1.805 +    // We've already set up our DBStates appropriately; nothing more to do.
   1.806 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
   1.807 +      ("InitDBStates(): couldn't get cookie file"));
   1.808 +    return;
   1.809 +  }
   1.810 +  mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
   1.811 +
   1.812 +  // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
   1.813 +  // do so.
   1.814 +  OpenDBResult result = TryInitDB(false);
   1.815 +  if (result == RESULT_RETRY) {
   1.816 +    // Database may be corrupt. Synchronously close the connection, clean up the
   1.817 +    // default DBState, and try again.
   1.818 +    COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()"));
   1.819 +    CleanupCachedStatements();
   1.820 +    CleanupDefaultDBConnection();
   1.821 +    result = TryInitDB(true);
   1.822 +    if (result == RESULT_RETRY) {
   1.823 +      // We're done. Change the code to failure so we clean up below.
   1.824 +      result = RESULT_FAILURE;
   1.825 +    }
   1.826 +  }
   1.827 +
   1.828 +  if (result == RESULT_FAILURE) {
   1.829 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
   1.830 +      ("InitDBStates(): TryInitDB() failed, closing connection"));
   1.831 +
   1.832 +    // Connection failure is unrecoverable. Clean up our connection. We can run
   1.833 +    // fine without persistent storage -- e.g. if there's no profile.
   1.834 +    CleanupCachedStatements();
   1.835 +    CleanupDefaultDBConnection();
   1.836 +  }
   1.837 +}
   1.838 +
   1.839 +/* Attempt to open and read the database. If 'aRecreateDB' is true, try to
   1.840 + * move the existing database file out of the way and create a new one.
   1.841 + *
   1.842 + * @returns RESULT_OK if opening or creating the database succeeded;
   1.843 + *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
   1.844 + *          other failure occurred that might be resolved by recreating the
   1.845 + *          database; or RESULT_FAILED if there was an unrecoverable error and
   1.846 + *          we must run without a database.
   1.847 + *
   1.848 + * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
   1.849 + * cleanup of the default DBState.
   1.850 + */
   1.851 +OpenDBResult
   1.852 +nsCookieService::TryInitDB(bool aRecreateDB)
   1.853 +{
   1.854 +  NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
   1.855 +  NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
   1.856 +  NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
   1.857 +  NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
   1.858 +
   1.859 +  // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
   1.860 +  // want to delete it outright, since it may be useful for debugging purposes,
   1.861 +  // so we move it out of the way.
   1.862 +  nsresult rv;
   1.863 +  if (aRecreateDB) {
   1.864 +    nsCOMPtr<nsIFile> backupFile;
   1.865 +    mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
   1.866 +    rv = backupFile->MoveToNative(nullptr,
   1.867 +      NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
   1.868 +    NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
   1.869 +  }
   1.870 +
   1.871 +  // This block provides scope for the Telemetry AutoTimer
   1.872 +  {
   1.873 +    Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
   1.874 +      telemetry;
   1.875 +    ReadAheadFile(mDefaultDBState->cookieFile);
   1.876 +
   1.877 +    // open a connection to the cookie database, and only cache our connection
   1.878 +    // and statements upon success. The connection is opened unshared to eliminate
   1.879 +    // cache contention between the main and background threads.
   1.880 +    rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
   1.881 +      getter_AddRefs(mDefaultDBState->dbConn));
   1.882 +    NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.883 +  }
   1.884 +
   1.885 +  // Set up our listeners.
   1.886 +  mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
   1.887 +  mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
   1.888 +  mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
   1.889 +  mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
   1.890 +
   1.891 +  // Grow cookie db in 512KB increments
   1.892 +  mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
   1.893 +
   1.894 +  bool tableExists = false;
   1.895 +  mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
   1.896 +    &tableExists);
   1.897 +  if (!tableExists) {
   1.898 +    rv = CreateTable();
   1.899 +    NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.900 +
   1.901 +  } else {
   1.902 +    // table already exists; check the schema version before reading
   1.903 +    int32_t dbSchemaVersion;
   1.904 +    rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
   1.905 +    NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.906 +
   1.907 +    // Start a transaction for the whole migration block.
   1.908 +    mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
   1.909 +
   1.910 +    switch (dbSchemaVersion) {
   1.911 +    // Upgrading.
   1.912 +    // Every time you increment the database schema, you need to implement
   1.913 +    // the upgrading code from the previous version to the new one. If migration
   1.914 +    // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
   1.915 +    // the original database will be saved, in the hopes that we might one day
   1.916 +    // see it and fix it.
   1.917 +    case 1:
   1.918 +      {
   1.919 +        // Add the lastAccessed column to the table.
   1.920 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   1.921 +          "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
   1.922 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.923 +      }
   1.924 +      // Fall through to the next upgrade.
   1.925 +
   1.926 +    case 2:
   1.927 +      {
   1.928 +        // Add the baseDomain column and index to the table.
   1.929 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   1.930 +          "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
   1.931 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.932 +
   1.933 +        // Compute the baseDomains for the table. This must be done eagerly
   1.934 +        // otherwise we won't be able to synchronously read in individual
   1.935 +        // domains on demand.
   1.936 +        const int64_t SCHEMA2_IDX_ID  =  0;
   1.937 +        const int64_t SCHEMA2_IDX_HOST = 1;
   1.938 +        nsCOMPtr<mozIStorageStatement> select;
   1.939 +        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   1.940 +          "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
   1.941 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.942 +
   1.943 +        nsCOMPtr<mozIStorageStatement> update;
   1.944 +        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   1.945 +          "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
   1.946 +          getter_AddRefs(update));
   1.947 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.948 +
   1.949 +        nsCString baseDomain, host;
   1.950 +        bool hasResult;
   1.951 +        while (1) {
   1.952 +          rv = select->ExecuteStep(&hasResult);
   1.953 +          NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.954 +
   1.955 +          if (!hasResult)
   1.956 +            break;
   1.957 +
   1.958 +          int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
   1.959 +          select->GetUTF8String(SCHEMA2_IDX_HOST, host);
   1.960 +
   1.961 +          rv = GetBaseDomainFromHost(host, baseDomain);
   1.962 +          NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.963 +
   1.964 +          mozStorageStatementScoper scoper(update);
   1.965 +
   1.966 +          rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
   1.967 +                                            baseDomain);
   1.968 +          NS_ASSERT_SUCCESS(rv);
   1.969 +          rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
   1.970 +                                       id);
   1.971 +          NS_ASSERT_SUCCESS(rv);
   1.972 +
   1.973 +          rv = update->ExecuteStep(&hasResult);
   1.974 +          NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.975 +        }
   1.976 +
   1.977 +        // Create an index on baseDomain.
   1.978 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   1.979 +          "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
   1.980 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   1.981 +      }
   1.982 +      // Fall through to the next upgrade.
   1.983 +
   1.984 +    case 3:
   1.985 +      {
   1.986 +        // Add the creationTime column to the table, and create a unique index
   1.987 +        // on (name, host, path). Before we do this, we have to purge the table
   1.988 +        // of expired cookies such that we know that the (name, host, path)
   1.989 +        // index is truly unique -- otherwise we can't create the index. Note
   1.990 +        // that we can't just execute a statement to delete all rows where the
   1.991 +        // expiry column is in the past -- doing so would rely on the clock
   1.992 +        // (both now and when previous cookies were set) being monotonic.
   1.993 +
   1.994 +        // Select the whole table, and order by the fields we're interested in.
   1.995 +        // This means we can simply do a linear traversal of the results and
   1.996 +        // check for duplicates as we go.
   1.997 +        const int64_t SCHEMA3_IDX_ID =   0;
   1.998 +        const int64_t SCHEMA3_IDX_NAME = 1;
   1.999 +        const int64_t SCHEMA3_IDX_HOST = 2;
  1.1000 +        const int64_t SCHEMA3_IDX_PATH = 3;
  1.1001 +        nsCOMPtr<mozIStorageStatement> select;
  1.1002 +        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1.1003 +          "SELECT id, name, host, path FROM moz_cookies "
  1.1004 +            "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
  1.1005 +          getter_AddRefs(select));
  1.1006 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1007 +
  1.1008 +        nsCOMPtr<mozIStorageStatement> deleteExpired;
  1.1009 +        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1.1010 +          "DELETE FROM moz_cookies WHERE id = :id"),
  1.1011 +          getter_AddRefs(deleteExpired));
  1.1012 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1013 +
  1.1014 +        // Read the first row.
  1.1015 +        bool hasResult;
  1.1016 +        rv = select->ExecuteStep(&hasResult);
  1.1017 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1018 +
  1.1019 +        if (hasResult) {
  1.1020 +          nsCString name1, host1, path1;
  1.1021 +          int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
  1.1022 +          select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
  1.1023 +          select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
  1.1024 +          select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
  1.1025 +
  1.1026 +          nsCString name2, host2, path2;
  1.1027 +          while (1) {
  1.1028 +            // Read the second row.
  1.1029 +            rv = select->ExecuteStep(&hasResult);
  1.1030 +            NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1031 +
  1.1032 +            if (!hasResult)
  1.1033 +              break;
  1.1034 +
  1.1035 +            int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
  1.1036 +            select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
  1.1037 +            select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
  1.1038 +            select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
  1.1039 +
  1.1040 +            // If the two rows match in (name, host, path), we know the earlier
  1.1041 +            // row has an earlier expiry time. Delete it.
  1.1042 +            if (name1 == name2 && host1 == host2 && path1 == path2) {
  1.1043 +              mozStorageStatementScoper scoper(deleteExpired);
  1.1044 +
  1.1045 +              rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
  1.1046 +                id1);
  1.1047 +              NS_ASSERT_SUCCESS(rv);
  1.1048 +
  1.1049 +              rv = deleteExpired->ExecuteStep(&hasResult);
  1.1050 +              NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1051 +            }
  1.1052 +
  1.1053 +            // Make the second row the first for the next iteration.
  1.1054 +            name1 = name2;
  1.1055 +            host1 = host2;
  1.1056 +            path1 = path2;
  1.1057 +            id1 = id2;
  1.1058 +          }
  1.1059 +        }
  1.1060 +
  1.1061 +        // Add the creationTime column to the table.
  1.1062 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1063 +          "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
  1.1064 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1065 +
  1.1066 +        // Copy the id of each row into the new creationTime column.
  1.1067 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1068 +          "UPDATE moz_cookies SET creationTime = "
  1.1069 +            "(SELECT id WHERE id = moz_cookies.id)"));
  1.1070 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1071 +
  1.1072 +        // Create a unique index on (name, host, path) to allow fast lookup.
  1.1073 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1074 +          "CREATE UNIQUE INDEX moz_uniqueid "
  1.1075 +          "ON moz_cookies (name, host, path)"));
  1.1076 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1077 +      }
  1.1078 +      // Fall through to the next upgrade.
  1.1079 +
  1.1080 +    case 4:
  1.1081 +      {
  1.1082 +        // We need to add appId/inBrowserElement, plus change a constraint on
  1.1083 +        // the table (unique entries now include appId/inBrowserElement):
  1.1084 +        // this requires creating a new table and copying the data to it.  We
  1.1085 +        // then rename the new table to the old name.
  1.1086 +        //
  1.1087 +        // Why we made this change: appId/inBrowserElement allow "cookie jars"
  1.1088 +        // for Firefox OS. We create a separate cookie namespace per {appId,
  1.1089 +        // inBrowserElement}.  When upgrading, we convert existing cookies
  1.1090 +        // (which imply we're on desktop/mobile) to use {0, false}, as that is
  1.1091 +        // the only namespace used by a non-Firefox-OS implementation.
  1.1092 +
  1.1093 +        // Rename existing table
  1.1094 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1095 +          "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
  1.1096 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1097 +
  1.1098 +        // Drop existing index (CreateTable will create new one for new table)
  1.1099 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1100 +          "DROP INDEX moz_basedomain"));
  1.1101 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1102 +
  1.1103 +        // Create new table (with new fields and new unique constraint)
  1.1104 +        rv = CreateTable();
  1.1105 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1106 +
  1.1107 +        // Copy data from old table, using appId/inBrowser=0 for existing rows
  1.1108 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1109 +          "INSERT INTO moz_cookies "
  1.1110 +          "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
  1.1111 +          " lastAccessed, creationTime, isSecure, isHttpOnly) "
  1.1112 +          "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
  1.1113 +          " lastAccessed, creationTime, isSecure, isHttpOnly "
  1.1114 +          "FROM moz_cookies_old"));
  1.1115 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1116 +
  1.1117 +        // Drop old table
  1.1118 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1119 +          "DROP TABLE moz_cookies_old"));
  1.1120 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1121 +
  1.1122 +        COOKIE_LOGSTRING(PR_LOG_DEBUG, 
  1.1123 +          ("Upgraded database to schema version 5"));
  1.1124 +      }
  1.1125 +
  1.1126 +      // No more upgrades. Update the schema version.
  1.1127 +      rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1.1128 +      NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1129 +
  1.1130 +    case COOKIES_SCHEMA_VERSION:
  1.1131 +      break;
  1.1132 +
  1.1133 +    case 0:
  1.1134 +      {
  1.1135 +        NS_WARNING("couldn't get schema version!");
  1.1136 +          
  1.1137 +        // the table may be usable; someone might've just clobbered the schema
  1.1138 +        // version. we can treat this case like a downgrade using the codepath
  1.1139 +        // below, by verifying the columns we care about are all there. for now,
  1.1140 +        // re-set the schema version in the db, in case the checks succeed (if
  1.1141 +        // they don't, we're dropping the table anyway).
  1.1142 +        rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1.1143 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1144 +      }
  1.1145 +      // fall through to downgrade check
  1.1146 +
  1.1147 +    // downgrading.
  1.1148 +    // if columns have been added to the table, we can still use the ones we
  1.1149 +    // understand safely. if columns have been deleted or altered, just
  1.1150 +    // blow away the table and start from scratch! if you change the way
  1.1151 +    // a column is interpreted, make sure you also change its name so this
  1.1152 +    // check will catch it.
  1.1153 +    default:
  1.1154 +      {
  1.1155 +        // check if all the expected columns exist
  1.1156 +        nsCOMPtr<mozIStorageStatement> stmt;
  1.1157 +        rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1.1158 +          "SELECT "
  1.1159 +            "id, "
  1.1160 +            "baseDomain, "
  1.1161 +            "appId, "
  1.1162 +            "inBrowserElement, "
  1.1163 +            "name, "
  1.1164 +            "value, "
  1.1165 +            "host, "
  1.1166 +            "path, "
  1.1167 +            "expiry, "
  1.1168 +            "lastAccessed, "
  1.1169 +            "creationTime, "
  1.1170 +            "isSecure, "
  1.1171 +            "isHttpOnly "
  1.1172 +          "FROM moz_cookies"), getter_AddRefs(stmt));
  1.1173 +        if (NS_SUCCEEDED(rv))
  1.1174 +          break;
  1.1175 +
  1.1176 +        // our columns aren't there - drop the table!
  1.1177 +        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1178 +          "DROP TABLE moz_cookies"));
  1.1179 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1180 +
  1.1181 +        rv = CreateTable();
  1.1182 +        NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1183 +      }
  1.1184 +      break;
  1.1185 +    }
  1.1186 +  }
  1.1187 +
  1.1188 +  // make operations on the table asynchronous, for performance
  1.1189 +  mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1190 +    "PRAGMA synchronous = OFF"));
  1.1191 +
  1.1192 +  // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
  1.1193 +  // 16 pages (around 500KB).
  1.1194 +  mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1195 +    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
  1.1196 +  mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1197 +    "PRAGMA wal_autocheckpoint = 16"));
  1.1198 +
  1.1199 +  // cache frequently used statements (for insertion, deletion, and updating)
  1.1200 +  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.1201 +    "INSERT INTO moz_cookies ("
  1.1202 +      "baseDomain, "
  1.1203 +      "appId, "
  1.1204 +      "inBrowserElement, "
  1.1205 +      "name, "
  1.1206 +      "value, "
  1.1207 +      "host, "
  1.1208 +      "path, "
  1.1209 +      "expiry, "
  1.1210 +      "lastAccessed, "
  1.1211 +      "creationTime, "
  1.1212 +      "isSecure, "
  1.1213 +      "isHttpOnly"
  1.1214 +    ") VALUES ("
  1.1215 +      ":baseDomain, "
  1.1216 +      ":appId, "
  1.1217 +      ":inBrowserElement, "
  1.1218 +      ":name, "
  1.1219 +      ":value, "
  1.1220 +      ":host, "
  1.1221 +      ":path, "
  1.1222 +      ":expiry, "
  1.1223 +      ":lastAccessed, "
  1.1224 +      ":creationTime, "
  1.1225 +      ":isSecure, "
  1.1226 +      ":isHttpOnly"
  1.1227 +    ")"),
  1.1228 +    getter_AddRefs(mDefaultDBState->stmtInsert));
  1.1229 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1230 +
  1.1231 +  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.1232 +    "DELETE FROM moz_cookies "
  1.1233 +    "WHERE name = :name AND host = :host AND path = :path"),
  1.1234 +    getter_AddRefs(mDefaultDBState->stmtDelete));
  1.1235 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1236 +
  1.1237 +  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.1238 +    "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
  1.1239 +    "WHERE name = :name AND host = :host AND path = :path"),
  1.1240 +    getter_AddRefs(mDefaultDBState->stmtUpdate));
  1.1241 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.1242 +
  1.1243 +  // if we deleted a corrupt db, don't attempt to import - return now
  1.1244 +  if (aRecreateDB)
  1.1245 +    return RESULT_OK;
  1.1246 +
  1.1247 +  // check whether to import or just read in the db
  1.1248 +  if (tableExists)
  1.1249 +    return Read();
  1.1250 +
  1.1251 +  nsCOMPtr<nsIFile> oldCookieFile;
  1.1252 +  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
  1.1253 +    getter_AddRefs(oldCookieFile));
  1.1254 +  if (NS_FAILED(rv)) return RESULT_OK;
  1.1255 +
  1.1256 +  // Import cookies, and clean up the old file regardless of success or failure.
  1.1257 +  // Note that we have to switch out our DBState temporarily, in case we're in
  1.1258 +  // private browsing mode; otherwise ImportCookies() won't be happy.
  1.1259 +  DBState* initialState = mDBState;
  1.1260 +  mDBState = mDefaultDBState;
  1.1261 +  oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
  1.1262 +  ImportCookies(oldCookieFile);
  1.1263 +  oldCookieFile->Remove(false);
  1.1264 +  mDBState = initialState;
  1.1265 +
  1.1266 +  return RESULT_OK;
  1.1267 +}
  1.1268 +
  1.1269 +// Sets the schema version and creates the moz_cookies table.
  1.1270 +nsresult
  1.1271 +nsCookieService::CreateTable()
  1.1272 +{
  1.1273 +  // Set the schema version, before creating the table.
  1.1274 +  nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
  1.1275 +    COOKIES_SCHEMA_VERSION);
  1.1276 +  if (NS_FAILED(rv)) return rv;
  1.1277 +
  1.1278 +  // Create the table.  We default appId/inBrowserElement to 0: this is so if
  1.1279 +  // users revert to an older Firefox version that doesn't know about these
  1.1280 +  // fields, any cookies set will still work once they upgrade back.
  1.1281 +  rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1282 +    "CREATE TABLE moz_cookies ("
  1.1283 +      "id INTEGER PRIMARY KEY, "
  1.1284 +      "baseDomain TEXT, "
  1.1285 +      "appId INTEGER DEFAULT 0, "
  1.1286 +      "inBrowserElement INTEGER DEFAULT 0, "
  1.1287 +      "name TEXT, "
  1.1288 +      "value TEXT, "
  1.1289 +      "host TEXT, "
  1.1290 +      "path TEXT, "
  1.1291 +      "expiry INTEGER, "
  1.1292 +      "lastAccessed INTEGER, "
  1.1293 +      "creationTime INTEGER, "
  1.1294 +      "isSecure INTEGER, "
  1.1295 +      "isHttpOnly INTEGER, "
  1.1296 +      "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
  1.1297 +    ")"));
  1.1298 +  if (NS_FAILED(rv)) return rv;
  1.1299 +
  1.1300 +  // Create an index on baseDomain.
  1.1301 +  return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1.1302 +    "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
  1.1303 +                                                "appId, "
  1.1304 +                                                "inBrowserElement)"));
  1.1305 +}
  1.1306 +
  1.1307 +void
  1.1308 +nsCookieService::CloseDBStates()
  1.1309 +{
  1.1310 +  // Null out our private and pointer DBStates regardless.
  1.1311 +  mPrivateDBState = nullptr;
  1.1312 +  mDBState = nullptr;
  1.1313 +
  1.1314 +  // If we don't have a default DBState, we're done.
  1.1315 +  if (!mDefaultDBState)
  1.1316 +    return;
  1.1317 +
  1.1318 +  // Cleanup cached statements before we can close anything.
  1.1319 +  CleanupCachedStatements();
  1.1320 +
  1.1321 +  if (mDefaultDBState->dbConn) {
  1.1322 +    // Cancel any pending read. No further results will be received by our
  1.1323 +    // read listener.
  1.1324 +    if (mDefaultDBState->pendingRead) {
  1.1325 +      CancelAsyncRead(true);
  1.1326 +    }
  1.1327 +
  1.1328 +    // Asynchronously close the connection. We will null it below.
  1.1329 +    mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1.1330 +  }
  1.1331 +
  1.1332 +  CleanupDefaultDBConnection();
  1.1333 +
  1.1334 +  mDefaultDBState = nullptr;
  1.1335 +}
  1.1336 +
  1.1337 +// Null out the statements.
  1.1338 +// This must be done before closing the connection.
  1.1339 +void
  1.1340 +nsCookieService::CleanupCachedStatements()
  1.1341 +{
  1.1342 +  mDefaultDBState->stmtInsert = nullptr;
  1.1343 +  mDefaultDBState->stmtDelete = nullptr;
  1.1344 +  mDefaultDBState->stmtUpdate = nullptr;
  1.1345 +}
  1.1346 +
  1.1347 +// Null out the listeners, and the database connection itself. This
  1.1348 +// will not null out the statements, cancel a pending read or
  1.1349 +// asynchronously close the connection -- these must be done
  1.1350 +// beforehand if necessary.
  1.1351 +void
  1.1352 +nsCookieService::CleanupDefaultDBConnection()
  1.1353 +{
  1.1354 +  MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
  1.1355 +  MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
  1.1356 +  MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
  1.1357 +
  1.1358 +  // Null out the database connections. If 'dbConn' has not been used for any
  1.1359 +  // asynchronous operations yet, this will synchronously close it; otherwise,
  1.1360 +  // it's expected that the caller has performed an AsyncClose prior.
  1.1361 +  mDefaultDBState->dbConn = nullptr;
  1.1362 +  mDefaultDBState->syncConn = nullptr;
  1.1363 +
  1.1364 +  // Manually null out our listeners. This is necessary because they hold a
  1.1365 +  // strong ref to the DBState itself. They'll stay alive until whatever
  1.1366 +  // statements are still executing complete.
  1.1367 +  mDefaultDBState->readListener = nullptr;
  1.1368 +  mDefaultDBState->insertListener = nullptr;
  1.1369 +  mDefaultDBState->updateListener = nullptr;
  1.1370 +  mDefaultDBState->removeListener = nullptr;
  1.1371 +  mDefaultDBState->closeListener = nullptr;
  1.1372 +}
  1.1373 +
  1.1374 +void
  1.1375 +nsCookieService::HandleDBClosed(DBState* aDBState)
  1.1376 +{
  1.1377 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.1378 +    ("HandleDBClosed(): DBState %x closed", aDBState));
  1.1379 +
  1.1380 +  switch (aDBState->corruptFlag) {
  1.1381 +  case DBState::OK: {
  1.1382 +    // Database is healthy. Notify of closure.
  1.1383 +    mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1.1384 +    break;
  1.1385 +  }
  1.1386 +  case DBState::CLOSING_FOR_REBUILD: {
  1.1387 +    // Our close finished. Start the rebuild, and notify of db closure later.
  1.1388 +    RebuildCorruptDB(aDBState);
  1.1389 +    break;
  1.1390 +  }
  1.1391 +  case DBState::REBUILDING: {
  1.1392 +    // We encountered an error during rebuild, closed the database, and now
  1.1393 +    // here we are. We already have a 'cookies.sqlite.bak' from the original
  1.1394 +    // dead database; we don't want to overwrite it, so let's move this one to
  1.1395 +    // 'cookies.sqlite.bak-rebuild'.
  1.1396 +    nsCOMPtr<nsIFile> backupFile;
  1.1397 +    aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
  1.1398 +    nsresult rv = backupFile->MoveToNative(nullptr,
  1.1399 +      NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
  1.1400 +
  1.1401 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
  1.1402 +      ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
  1.1403 +       "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
  1.1404 +    mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1.1405 +    break;
  1.1406 +  }
  1.1407 +  }
  1.1408 +}
  1.1409 +
  1.1410 +void
  1.1411 +nsCookieService::HandleCorruptDB(DBState* aDBState)
  1.1412 +{
  1.1413 +  if (mDefaultDBState != aDBState) {
  1.1414 +    // We've either closed the state or we've switched profiles. It's getting
  1.1415 +    // a bit late to rebuild -- bail instead.
  1.1416 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
  1.1417 +      ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
  1.1418 +    return;
  1.1419 +  }
  1.1420 +
  1.1421 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.1422 +    ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
  1.1423 +      aDBState->corruptFlag));
  1.1424 +
  1.1425 +  // Mark the database corrupt, so the close listener can begin reconstructing
  1.1426 +  // it.
  1.1427 +  switch (mDefaultDBState->corruptFlag) {
  1.1428 +  case DBState::OK: {
  1.1429 +    // Move to 'closing' state.
  1.1430 +    mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
  1.1431 +
  1.1432 +    // Cancel any pending read and close the database. If we do have an
  1.1433 +    // in-flight read we want to throw away all the results so far -- we have no
  1.1434 +    // idea how consistent the database is. Note that we may have already
  1.1435 +    // canceled the read but not emptied our readSet; do so now.
  1.1436 +    mDefaultDBState->readSet.Clear();
  1.1437 +    if (mDefaultDBState->pendingRead) {
  1.1438 +      CancelAsyncRead(true);
  1.1439 +      mDefaultDBState->syncConn = nullptr;
  1.1440 +    }
  1.1441 +
  1.1442 +    CleanupCachedStatements();
  1.1443 +    mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1.1444 +    CleanupDefaultDBConnection();
  1.1445 +    break;
  1.1446 +  }
  1.1447 +  case DBState::CLOSING_FOR_REBUILD: {
  1.1448 +    // We had an error while waiting for close completion. That's OK, just
  1.1449 +    // ignore it -- we're rebuilding anyway.
  1.1450 +    return;
  1.1451 +  }
  1.1452 +  case DBState::REBUILDING: {
  1.1453 +    // We had an error while rebuilding the DB. Game over. Close the database
  1.1454 +    // and let the close handler do nothing; then we'll move it out of the way.
  1.1455 +    CleanupCachedStatements();
  1.1456 +    if (mDefaultDBState->dbConn) {
  1.1457 +      mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1.1458 +    }
  1.1459 +    CleanupDefaultDBConnection();
  1.1460 +    break;
  1.1461 +  }
  1.1462 +  }
  1.1463 +}
  1.1464 +
  1.1465 +static PLDHashOperator
  1.1466 +RebuildDBCallback(nsCookieEntry *aEntry,
  1.1467 +                  void          *aArg)
  1.1468 +{
  1.1469 +  mozIStorageBindingParamsArray* paramsArray =
  1.1470 +    static_cast<mozIStorageBindingParamsArray*>(aArg);
  1.1471 +
  1.1472 +  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1.1473 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.1474 +    nsCookie* cookie = cookies[i];
  1.1475 +
  1.1476 +    if (!cookie->IsSession()) {
  1.1477 +      bindCookieParameters(paramsArray, aEntry, cookie);
  1.1478 +    }
  1.1479 +  }
  1.1480 +
  1.1481 +  return PL_DHASH_NEXT;
  1.1482 +}
  1.1483 +
  1.1484 +void
  1.1485 +nsCookieService::RebuildCorruptDB(DBState* aDBState)
  1.1486 +{
  1.1487 +  NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
  1.1488 +  NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
  1.1489 +    "should be in CLOSING_FOR_REBUILD state");
  1.1490 +
  1.1491 +  aDBState->corruptFlag = DBState::REBUILDING;
  1.1492 +
  1.1493 +  if (mDefaultDBState != aDBState) {
  1.1494 +    // We've either closed the state or we've switched profiles. It's getting
  1.1495 +    // a bit late to rebuild -- bail instead. In any case, we were waiting
  1.1496 +    // on rebuild completion to notify of the db closure, which won't happen --
  1.1497 +    // do so now.
  1.1498 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
  1.1499 +      ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
  1.1500 +    mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1.1501 +    return;
  1.1502 +  }
  1.1503 +
  1.1504 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.1505 +    ("RebuildCorruptDB(): creating new database"));
  1.1506 +
  1.1507 +  // The database has been closed, and we're ready to rebuild. Open a
  1.1508 +  // connection.
  1.1509 +  OpenDBResult result = TryInitDB(true);
  1.1510 +  if (result != RESULT_OK) {
  1.1511 +    // We're done. Reset our DB connection and statements, and notify of
  1.1512 +    // closure.
  1.1513 +    COOKIE_LOGSTRING(PR_LOG_WARNING,
  1.1514 +      ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
  1.1515 +    CleanupCachedStatements();
  1.1516 +    CleanupDefaultDBConnection();
  1.1517 +    mDefaultDBState->corruptFlag = DBState::OK;
  1.1518 +    mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1.1519 +    return;
  1.1520 +  }
  1.1521 +
  1.1522 +  // Notify observers that we're beginning the rebuild.
  1.1523 +  mObserverService->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
  1.1524 +
  1.1525 +  // Enumerate the hash, and add cookies to the params array.
  1.1526 +  mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
  1.1527 +  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1.1528 +  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.1529 +  aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get());
  1.1530 +
  1.1531 +  // Make sure we've got something to write. If we don't, we're done.
  1.1532 +  uint32_t length;
  1.1533 +  paramsArray->GetLength(&length);
  1.1534 +  if (length == 0) {
  1.1535 +    COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.1536 +      ("RebuildCorruptDB(): nothing to write, rebuild complete"));
  1.1537 +    mDefaultDBState->corruptFlag = DBState::OK;
  1.1538 +    return;
  1.1539 +  }
  1.1540 +
  1.1541 +  // Execute the statement. If any errors crop up, we won't try again.
  1.1542 +  DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1.1543 +  NS_ASSERT_SUCCESS(rv);
  1.1544 +  nsCOMPtr<mozIStoragePendingStatement> handle;
  1.1545 +  rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
  1.1546 +  NS_ASSERT_SUCCESS(rv);
  1.1547 +}
  1.1548 +
  1.1549 +nsCookieService::~nsCookieService()
  1.1550 +{
  1.1551 +  CloseDBStates();
  1.1552 +
  1.1553 +  UnregisterWeakMemoryReporter(this);
  1.1554 +
  1.1555 +  gCookieService = nullptr;
  1.1556 +}
  1.1557 +
  1.1558 +NS_IMETHODIMP
  1.1559 +nsCookieService::Observe(nsISupports     *aSubject,
  1.1560 +                         const char      *aTopic,
  1.1561 +                         const char16_t *aData)
  1.1562 +{
  1.1563 +  // check the topic
  1.1564 +  if (!strcmp(aTopic, "profile-before-change")) {
  1.1565 +    // The profile is about to change,
  1.1566 +    // or is going away because the application is shutting down.
  1.1567 +    if (mDBState && mDBState->dbConn &&
  1.1568 +        !nsCRT::strcmp(aData, MOZ_UTF16("shutdown-cleanse"))) {
  1.1569 +      // Clear the cookie db if we're in the default DBState.
  1.1570 +      RemoveAll();
  1.1571 +    }
  1.1572 +
  1.1573 +    // Close the default DB connection and null out our DBStates before
  1.1574 +    // changing.
  1.1575 +    CloseDBStates();
  1.1576 +
  1.1577 +  } else if (!strcmp(aTopic, "profile-do-change")) {
  1.1578 +    NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
  1.1579 +    NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
  1.1580 +
  1.1581 +    // the profile has already changed; init the db from the new location.
  1.1582 +    // if we are in the private browsing state, however, we do not want to read
  1.1583 +    // data into it - we should instead put it into the default state, so it's
  1.1584 +    // ready for us if and when we switch back to it.
  1.1585 +    InitDBStates();
  1.1586 +
  1.1587 +  } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
  1.1588 +    nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
  1.1589 +    if (prefBranch)
  1.1590 +      PrefChanged(prefBranch);
  1.1591 +
  1.1592 +  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
  1.1593 +    // Flush all the cookies stored by private browsing contexts
  1.1594 +    mPrivateDBState = new DBState();
  1.1595 +  }
  1.1596 +
  1.1597 +
  1.1598 +  return NS_OK;
  1.1599 +}
  1.1600 +
  1.1601 +NS_IMETHODIMP
  1.1602 +nsCookieService::GetCookieString(nsIURI     *aHostURI,
  1.1603 +                                 nsIChannel *aChannel,
  1.1604 +                                 char       **aCookie)
  1.1605 +{
  1.1606 +  return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
  1.1607 +}
  1.1608 +
  1.1609 +NS_IMETHODIMP
  1.1610 +nsCookieService::GetCookieStringFromHttp(nsIURI     *aHostURI,
  1.1611 +                                         nsIURI     *aFirstURI,
  1.1612 +                                         nsIChannel *aChannel,
  1.1613 +                                         char       **aCookie)
  1.1614 +{
  1.1615 +  return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
  1.1616 +}
  1.1617 +
  1.1618 +nsresult
  1.1619 +nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
  1.1620 +                                       nsIChannel *aChannel,
  1.1621 +                                       bool aHttpBound,
  1.1622 +                                       char** aCookie)
  1.1623 +{
  1.1624 +  NS_ENSURE_ARG(aHostURI);
  1.1625 +  NS_ENSURE_ARG(aCookie);
  1.1626 +
  1.1627 +  // Determine whether the request is foreign. Failure is acceptable.
  1.1628 +  bool isForeign = true;
  1.1629 +  mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1.1630 +
  1.1631 +  // Get app info, if channel is present.  Else assume default namespace.
  1.1632 +  uint32_t appId = NECKO_NO_APP_ID;
  1.1633 +  bool inBrowserElement = false;
  1.1634 +  if (aChannel) {
  1.1635 +    NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1.1636 +  }
  1.1637 +
  1.1638 +  bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1.1639 +
  1.1640 +  nsAutoCString result;
  1.1641 +  GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
  1.1642 +                          inBrowserElement, isPrivate, result);
  1.1643 +  *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
  1.1644 +  return NS_OK;
  1.1645 +}
  1.1646 +
  1.1647 +NS_IMETHODIMP
  1.1648 +nsCookieService::SetCookieString(nsIURI     *aHostURI,
  1.1649 +                                 nsIPrompt  *aPrompt,
  1.1650 +                                 const char *aCookieHeader,
  1.1651 +                                 nsIChannel *aChannel)
  1.1652 +{
  1.1653 +  // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1.1654 +  // code that uses this argument by warning if the value is non-null.
  1.1655 +  MOZ_ASSERT(!aPrompt);
  1.1656 +  if (aPrompt) {
  1.1657 +    nsCOMPtr<nsIConsoleService> aConsoleService =
  1.1658 +        do_GetService("@mozilla.org/consoleservice;1");
  1.1659 +    if (aConsoleService) {
  1.1660 +      aConsoleService->LogStringMessage(
  1.1661 +        MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1.1662 +    }
  1.1663 +  }
  1.1664 +  return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
  1.1665 +                               false);
  1.1666 +}
  1.1667 +
  1.1668 +NS_IMETHODIMP
  1.1669 +nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
  1.1670 +                                         nsIURI     *aFirstURI,
  1.1671 +                                         nsIPrompt  *aPrompt,
  1.1672 +                                         const char *aCookieHeader,
  1.1673 +                                         const char *aServerTime,
  1.1674 +                                         nsIChannel *aChannel) 
  1.1675 +{
  1.1676 +  // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1.1677 +  // code that uses this argument by warning if the value is non-null.
  1.1678 +  MOZ_ASSERT(!aPrompt);
  1.1679 +  if (aPrompt) {
  1.1680 +    nsCOMPtr<nsIConsoleService> aConsoleService =
  1.1681 +        do_GetService("@mozilla.org/consoleservice;1");
  1.1682 +    if (aConsoleService) {
  1.1683 +      aConsoleService->LogStringMessage(
  1.1684 +        MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1.1685 +    }
  1.1686 +  }
  1.1687 +  return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
  1.1688 +                               true);
  1.1689 +}
  1.1690 +
  1.1691 +nsresult
  1.1692 +nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
  1.1693 +                                       const char *aCookieHeader,
  1.1694 +                                       const char *aServerTime,
  1.1695 +                                       nsIChannel *aChannel,
  1.1696 +                                       bool aFromHttp) 
  1.1697 +{
  1.1698 +  NS_ENSURE_ARG(aHostURI);
  1.1699 +  NS_ENSURE_ARG(aCookieHeader);
  1.1700 +
  1.1701 +  // Determine whether the request is foreign. Failure is acceptable.
  1.1702 +  bool isForeign = true;
  1.1703 +  mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1.1704 +
  1.1705 +  // Get app info, if channel is present.  Else assume default namespace.
  1.1706 +  uint32_t appId = NECKO_NO_APP_ID;
  1.1707 +  bool inBrowserElement = false;
  1.1708 +  if (aChannel) {
  1.1709 +    NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1.1710 +  }
  1.1711 +
  1.1712 +  bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1.1713 +
  1.1714 +  nsDependentCString cookieString(aCookieHeader);
  1.1715 +  nsDependentCString serverTime(aServerTime ? aServerTime : "");
  1.1716 +  SetCookieStringInternal(aHostURI, isForeign, cookieString,
  1.1717 +                          serverTime, aFromHttp, appId, inBrowserElement,
  1.1718 +                          isPrivate, aChannel);
  1.1719 +  return NS_OK;
  1.1720 +}
  1.1721 +
  1.1722 +void
  1.1723 +nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
  1.1724 +                                         bool                aIsForeign,
  1.1725 +                                         nsDependentCString &aCookieHeader,
  1.1726 +                                         const nsCString    &aServerTime,
  1.1727 +                                         bool                aFromHttp,
  1.1728 +                                         uint32_t            aAppId,
  1.1729 +                                         bool                aInBrowserElement,
  1.1730 +                                         bool                aIsPrivate,
  1.1731 +                                         nsIChannel         *aChannel)
  1.1732 +{
  1.1733 +  NS_ASSERTION(aHostURI, "null host!");
  1.1734 +
  1.1735 +  if (!mDBState) {
  1.1736 +    NS_WARNING("No DBState! Profile already closed?");
  1.1737 +    return;
  1.1738 +  }
  1.1739 +
  1.1740 +  AutoRestore<DBState*> savePrevDBState(mDBState);
  1.1741 +  mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  1.1742 +
  1.1743 +  // get the base domain for the host URI.
  1.1744 +  // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  1.1745 +  // file:// URI's (i.e. with an empty host) are allowed, but any other
  1.1746 +  // scheme must have a non-empty host. A trailing dot in the host
  1.1747 +  // is acceptable.
  1.1748 +  bool requireHostMatch;
  1.1749 +  nsAutoCString baseDomain;
  1.1750 +  nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  1.1751 +  if (NS_FAILED(rv)) {
  1.1752 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
  1.1753 +                      "couldn't get base domain from URI");
  1.1754 +    return;
  1.1755 +  }
  1.1756 +
  1.1757 +  nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  1.1758 +
  1.1759 +  // check default prefs
  1.1760 +  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  1.1761 +                                         aCookieHeader.get());
  1.1762 +  // fire a notification if third party or if cookie was rejected
  1.1763 +  // (but not if there was an error)
  1.1764 +  switch (cookieStatus) {
  1.1765 +  case STATUS_REJECTED:
  1.1766 +    NotifyRejected(aHostURI);
  1.1767 +    if (aIsForeign) {
  1.1768 +      NotifyThirdParty(aHostURI, false, aChannel);
  1.1769 +    }
  1.1770 +    return; // Stop here
  1.1771 +  case STATUS_REJECTED_WITH_ERROR:
  1.1772 +    return;
  1.1773 +  case STATUS_ACCEPTED: // Fallthrough
  1.1774 +  case STATUS_ACCEPT_SESSION:
  1.1775 +    if (aIsForeign) {
  1.1776 +      NotifyThirdParty(aHostURI, true, aChannel);
  1.1777 +    }
  1.1778 +    break;
  1.1779 +  default:
  1.1780 +    break;
  1.1781 +  }
  1.1782 +
  1.1783 +  // parse server local time. this is not just done here for efficiency
  1.1784 +  // reasons - if there's an error parsing it, and we need to default it
  1.1785 +  // to the current time, we must do it here since the current time in
  1.1786 +  // SetCookieInternal() will change for each cookie processed (e.g. if the
  1.1787 +  // user is prompted).
  1.1788 +  PRTime tempServerTime;
  1.1789 +  int64_t serverTime;
  1.1790 +  PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
  1.1791 +                                       &tempServerTime);
  1.1792 +  if (result == PR_SUCCESS) {
  1.1793 +    serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
  1.1794 +  } else {
  1.1795 +    serverTime = PR_Now() / PR_USEC_PER_SEC;
  1.1796 +  }
  1.1797 +
  1.1798 +  // process each cookie in the header
  1.1799 +  while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
  1.1800 +                           aCookieHeader, serverTime, aFromHttp, aChannel)) {
  1.1801 +    // document.cookie can only set one cookie at a time
  1.1802 +    if (!aFromHttp)
  1.1803 +      break;
  1.1804 +  }
  1.1805 +}
  1.1806 +
  1.1807 +// notify observers that a cookie was rejected due to the users' prefs.
  1.1808 +void
  1.1809 +nsCookieService::NotifyRejected(nsIURI *aHostURI)
  1.1810 +{
  1.1811 +  if (mObserverService) {
  1.1812 +    mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
  1.1813 +  }
  1.1814 +}
  1.1815 +
  1.1816 +// notify observers that a third-party cookie was accepted/rejected
  1.1817 +// if the cookie issuer is unknown, it defaults to "?"
  1.1818 +void
  1.1819 +nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
  1.1820 +{
  1.1821 +  if (!mObserverService) {
  1.1822 +    return;
  1.1823 +  }
  1.1824 +
  1.1825 +  const char* topic;
  1.1826 +
  1.1827 +  if (mDBState != mPrivateDBState) {
  1.1828 +    // Regular (non-private) browsing
  1.1829 +    if (aIsAccepted) {
  1.1830 +      topic = "third-party-cookie-accepted";
  1.1831 +    } else {
  1.1832 +      topic = "third-party-cookie-rejected";
  1.1833 +    }
  1.1834 +  } else {
  1.1835 +    // Private browsing
  1.1836 +    if (aIsAccepted) {
  1.1837 +      topic = "private-third-party-cookie-accepted";
  1.1838 +    } else {
  1.1839 +      topic = "private-third-party-cookie-rejected";
  1.1840 +    }
  1.1841 +  }
  1.1842 +
  1.1843 +  do {
  1.1844 +    // Attempt to find the host of aChannel.
  1.1845 +    if (!aChannel) {
  1.1846 +      break;
  1.1847 +    }
  1.1848 +    nsCOMPtr<nsIURI> channelURI;
  1.1849 +    nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
  1.1850 +    if (NS_FAILED(rv)) {
  1.1851 +      break;
  1.1852 +    }
  1.1853 +
  1.1854 +    nsAutoCString referringHost;
  1.1855 +    rv = channelURI->GetHost(referringHost);
  1.1856 +    if (NS_FAILED(rv)) {
  1.1857 +      break;
  1.1858 +    }
  1.1859 +
  1.1860 +    nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
  1.1861 +    mObserverService->NotifyObservers(aHostURI,
  1.1862 +                                      topic,
  1.1863 +                                      referringHostUTF16.get());
  1.1864 +    return;
  1.1865 +  } while (0);
  1.1866 +
  1.1867 +  // This can fail for a number of reasons, in which kind we fallback to "?"
  1.1868 +  mObserverService->NotifyObservers(aHostURI,
  1.1869 +                                    topic,
  1.1870 +                                    MOZ_UTF16("?"));
  1.1871 +}
  1.1872 +
  1.1873 +// notify observers that the cookie list changed. there are five possible
  1.1874 +// values for aData:
  1.1875 +// "deleted" means a cookie was deleted. aSubject is the deleted cookie.
  1.1876 +// "added"   means a cookie was added. aSubject is the added cookie.
  1.1877 +// "changed" means a cookie was altered. aSubject is the new cookie.
  1.1878 +// "cleared" means the entire cookie list was cleared. aSubject is null.
  1.1879 +// "batch-deleted" means a set of cookies was purged. aSubject is the list of
  1.1880 +// cookies.
  1.1881 +void
  1.1882 +nsCookieService::NotifyChanged(nsISupports     *aSubject,
  1.1883 +                               const char16_t *aData)
  1.1884 +{
  1.1885 +  const char* topic = mDBState == mPrivateDBState ?
  1.1886 +      "private-cookie-changed" : "cookie-changed";
  1.1887 +  if (mObserverService)
  1.1888 +    mObserverService->NotifyObservers(aSubject, topic, aData);
  1.1889 +}
  1.1890 +
  1.1891 +already_AddRefed<nsIArray>
  1.1892 +nsCookieService::CreatePurgeList(nsICookie2* aCookie)
  1.1893 +{
  1.1894 +  nsCOMPtr<nsIMutableArray> removedList =
  1.1895 +    do_CreateInstance(NS_ARRAY_CONTRACTID);
  1.1896 +  removedList->AppendElement(aCookie, false);
  1.1897 +  return removedList.forget();
  1.1898 +}
  1.1899 +
  1.1900 +/******************************************************************************
  1.1901 + * nsCookieService:
  1.1902 + * pref observer impl
  1.1903 + ******************************************************************************/
  1.1904 +
  1.1905 +void
  1.1906 +nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
  1.1907 +{
  1.1908 +  int32_t val;
  1.1909 +  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
  1.1910 +    mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
  1.1911 +
  1.1912 +  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
  1.1913 +    mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
  1.1914 +
  1.1915 +  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
  1.1916 +    mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
  1.1917 +
  1.1918 +  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
  1.1919 +    mCookiePurgeAge =
  1.1920 +      int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
  1.1921 +  }
  1.1922 +
  1.1923 +  bool boolval;
  1.1924 +  if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
  1.1925 +    mThirdPartySession = boolval;
  1.1926 +}
  1.1927 +
  1.1928 +/******************************************************************************
  1.1929 + * nsICookieManager impl:
  1.1930 + * nsICookieManager
  1.1931 + ******************************************************************************/
  1.1932 +
  1.1933 +NS_IMETHODIMP
  1.1934 +nsCookieService::RemoveAll()
  1.1935 +{
  1.1936 +  if (!mDBState) {
  1.1937 +    NS_WARNING("No DBState! Profile already closed?");
  1.1938 +    return NS_ERROR_NOT_AVAILABLE;
  1.1939 +  }
  1.1940 +
  1.1941 +  RemoveAllFromMemory();
  1.1942 +
  1.1943 +  // clear the cookie file
  1.1944 +  if (mDBState->dbConn) {
  1.1945 +    NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
  1.1946 +
  1.1947 +    // Cancel any pending read. No further results will be received by our
  1.1948 +    // read listener.
  1.1949 +    if (mDefaultDBState->pendingRead) {
  1.1950 +      CancelAsyncRead(true);
  1.1951 +    }
  1.1952 +
  1.1953 +    nsCOMPtr<mozIStorageAsyncStatement> stmt;
  1.1954 +    nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.1955 +      "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
  1.1956 +    if (NS_SUCCEEDED(rv)) {
  1.1957 +      nsCOMPtr<mozIStoragePendingStatement> handle;
  1.1958 +      rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
  1.1959 +        getter_AddRefs(handle));
  1.1960 +      NS_ASSERT_SUCCESS(rv);
  1.1961 +    } else {
  1.1962 +      // Recreate the database.
  1.1963 +      COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.1964 +        ("RemoveAll(): corruption detected with rv 0x%x", rv));
  1.1965 +      HandleCorruptDB(mDefaultDBState);
  1.1966 +    }
  1.1967 +  }
  1.1968 +
  1.1969 +  NotifyChanged(nullptr, MOZ_UTF16("cleared"));
  1.1970 +  return NS_OK;
  1.1971 +}
  1.1972 +
  1.1973 +static PLDHashOperator
  1.1974 +COMArrayCallback(nsCookieEntry *aEntry,
  1.1975 +                 void          *aArg)
  1.1976 +{
  1.1977 +  nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
  1.1978 +
  1.1979 +  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1.1980 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.1981 +    data->AppendObject(cookies[i]);
  1.1982 +  }
  1.1983 +
  1.1984 +  return PL_DHASH_NEXT;
  1.1985 +}
  1.1986 +
  1.1987 +NS_IMETHODIMP
  1.1988 +nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
  1.1989 +{
  1.1990 +  if (!mDBState) {
  1.1991 +    NS_WARNING("No DBState! Profile already closed?");
  1.1992 +    return NS_ERROR_NOT_AVAILABLE;
  1.1993 +  }
  1.1994 +
  1.1995 +  EnsureReadComplete();
  1.1996 +
  1.1997 +  nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
  1.1998 +  mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
  1.1999 +
  1.2000 +  return NS_NewArrayEnumerator(aEnumerator, cookieList);
  1.2001 +}
  1.2002 +
  1.2003 +NS_IMETHODIMP
  1.2004 +nsCookieService::Add(const nsACString &aHost,
  1.2005 +                     const nsACString &aPath,
  1.2006 +                     const nsACString &aName,
  1.2007 +                     const nsACString &aValue,
  1.2008 +                     bool              aIsSecure,
  1.2009 +                     bool              aIsHttpOnly,
  1.2010 +                     bool              aIsSession,
  1.2011 +                     int64_t           aExpiry)
  1.2012 +{
  1.2013 +  if (!mDBState) {
  1.2014 +    NS_WARNING("No DBState! Profile already closed?");
  1.2015 +    return NS_ERROR_NOT_AVAILABLE;
  1.2016 +  }
  1.2017 +
  1.2018 +  // first, normalize the hostname, and fail if it contains illegal characters.
  1.2019 +  nsAutoCString host(aHost);
  1.2020 +  nsresult rv = NormalizeHost(host);
  1.2021 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2022 +
  1.2023 +  // get the base domain for the host URI.
  1.2024 +  // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  1.2025 +  nsAutoCString baseDomain;
  1.2026 +  rv = GetBaseDomainFromHost(host, baseDomain);
  1.2027 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2028 +
  1.2029 +  int64_t currentTimeInUsec = PR_Now();
  1.2030 +
  1.2031 +  nsRefPtr<nsCookie> cookie =
  1.2032 +    nsCookie::Create(aName, aValue, host, aPath,
  1.2033 +                     aExpiry,
  1.2034 +                     currentTimeInUsec,
  1.2035 +                     nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  1.2036 +                     aIsSession,
  1.2037 +                     aIsSecure,
  1.2038 +                     aIsHttpOnly);
  1.2039 +  if (!cookie) {
  1.2040 +    return NS_ERROR_OUT_OF_MEMORY;
  1.2041 +  }
  1.2042 +
  1.2043 +  AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
  1.2044 +  return NS_OK;
  1.2045 +}
  1.2046 +
  1.2047 +
  1.2048 +nsresult
  1.2049 +nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId,
  1.2050 +                        bool aInBrowserElement, const nsACString& aName,
  1.2051 +                        const nsACString& aPath, bool aBlocked)
  1.2052 +{
  1.2053 +  if (!mDBState) {
  1.2054 +    NS_WARNING("No DBState! Profile already closed?");
  1.2055 +    return NS_ERROR_NOT_AVAILABLE;
  1.2056 +  }
  1.2057 +
  1.2058 +  // first, normalize the hostname, and fail if it contains illegal characters.
  1.2059 +  nsAutoCString host(aHost);
  1.2060 +  nsresult rv = NormalizeHost(host);
  1.2061 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2062 +
  1.2063 +  nsAutoCString baseDomain;
  1.2064 +  rv = GetBaseDomainFromHost(host, baseDomain);
  1.2065 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2066 +
  1.2067 +  nsListIter matchIter;
  1.2068 +  nsRefPtr<nsCookie> cookie;
  1.2069 +  if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement),
  1.2070 +                 host,
  1.2071 +                 PromiseFlatCString(aName),
  1.2072 +                 PromiseFlatCString(aPath),
  1.2073 +                 matchIter)) {
  1.2074 +    cookie = matchIter.Cookie();
  1.2075 +    RemoveCookieFromList(matchIter);
  1.2076 +  }
  1.2077 +
  1.2078 +  // check if we need to add the host to the permissions blacklist.
  1.2079 +  if (aBlocked && mPermissionService) {
  1.2080 +    // strip off the domain dot, if necessary
  1.2081 +    if (!host.IsEmpty() && host.First() == '.')
  1.2082 +      host.Cut(0, 1);
  1.2083 +
  1.2084 +    host.Insert(NS_LITERAL_CSTRING("http://"), 0);
  1.2085 +
  1.2086 +    nsCOMPtr<nsIURI> uri;
  1.2087 +    NS_NewURI(getter_AddRefs(uri), host);
  1.2088 +
  1.2089 +    if (uri)
  1.2090 +      mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
  1.2091 +  }
  1.2092 +
  1.2093 +  if (cookie) {
  1.2094 +    // Everything's done. Notify observers.
  1.2095 +    NotifyChanged(cookie, MOZ_UTF16("deleted"));
  1.2096 +  }
  1.2097 +
  1.2098 +  return NS_OK;
  1.2099 +}
  1.2100 +
  1.2101 +NS_IMETHODIMP
  1.2102 +nsCookieService::Remove(const nsACString &aHost,
  1.2103 +                        const nsACString &aName,
  1.2104 +                        const nsACString &aPath,
  1.2105 +                        bool             aBlocked)
  1.2106 +{
  1.2107 +  return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked);
  1.2108 +}
  1.2109 +
  1.2110 +/******************************************************************************
  1.2111 + * nsCookieService impl:
  1.2112 + * private file I/O functions
  1.2113 + ******************************************************************************/
  1.2114 +
  1.2115 +// Begin an asynchronous read from the database.
  1.2116 +OpenDBResult
  1.2117 +nsCookieService::Read()
  1.2118 +{
  1.2119 +  // Set up a statement for the read. Note that our query specifies that
  1.2120 +  // 'baseDomain' not be nullptr -- see below for why.
  1.2121 +  nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
  1.2122 +  nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.2123 +    "SELECT "
  1.2124 +      "name, "
  1.2125 +      "value, "
  1.2126 +      "host, "
  1.2127 +      "path, "
  1.2128 +      "expiry, "
  1.2129 +      "lastAccessed, "
  1.2130 +      "creationTime, "
  1.2131 +      "isSecure, "
  1.2132 +      "isHttpOnly, "
  1.2133 +      "baseDomain, "
  1.2134 +      "appId,  "
  1.2135 +      "inBrowserElement "
  1.2136 +    "FROM moz_cookies "
  1.2137 +    "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
  1.2138 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.2139 +
  1.2140 +  // Set up a statement to delete any rows with a nullptr 'baseDomain'
  1.2141 +  // column. This takes care of any cookies set by browsers that don't
  1.2142 +  // understand the 'baseDomain' column, where the database schema version
  1.2143 +  // is from one that does. (This would occur when downgrading.)
  1.2144 +  nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
  1.2145 +  rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.2146 +    "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
  1.2147 +    getter_AddRefs(stmtDeleteNull));
  1.2148 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.2149 +
  1.2150 +  // Start a new connection for sync reads, to reduce contention with the
  1.2151 +  // background thread. We need to do this before we kick off write statements,
  1.2152 +  // since they can lock the database and prevent connections from being opened.
  1.2153 +  rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
  1.2154 +    getter_AddRefs(mDefaultDBState->syncConn));
  1.2155 +  NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1.2156 +
  1.2157 +  // Init our readSet hash and execute the statements. Note that, after this
  1.2158 +  // point, we cannot fail without altering the cleanup code in InitDBStates()
  1.2159 +  // to handle closing of the now-asynchronous connection.
  1.2160 +  mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
  1.2161 +
  1.2162 +  mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
  1.2163 +  rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
  1.2164 +    getter_AddRefs(mDefaultDBState->pendingRead));
  1.2165 +  NS_ASSERT_SUCCESS(rv);
  1.2166 +
  1.2167 +  nsCOMPtr<mozIStoragePendingStatement> handle;
  1.2168 +  rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
  1.2169 +    getter_AddRefs(handle));
  1.2170 +  NS_ASSERT_SUCCESS(rv);
  1.2171 +
  1.2172 +  return RESULT_OK;
  1.2173 +}
  1.2174 +
  1.2175 +// Extract data from a single result row and create an nsCookie.
  1.2176 +// This is templated since 'T' is different for sync vs async results.
  1.2177 +template<class T> nsCookie*
  1.2178 +nsCookieService::GetCookieFromRow(T &aRow)
  1.2179 +{
  1.2180 +  // Skip reading 'baseDomain' -- up to the caller.
  1.2181 +  nsCString name, value, host, path;
  1.2182 +  DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
  1.2183 +  NS_ASSERT_SUCCESS(rv);
  1.2184 +  rv = aRow->GetUTF8String(IDX_VALUE, value);
  1.2185 +  NS_ASSERT_SUCCESS(rv);
  1.2186 +  rv = aRow->GetUTF8String(IDX_HOST, host);
  1.2187 +  NS_ASSERT_SUCCESS(rv);
  1.2188 +  rv = aRow->GetUTF8String(IDX_PATH, path);
  1.2189 +  NS_ASSERT_SUCCESS(rv);
  1.2190 +
  1.2191 +  int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
  1.2192 +  int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
  1.2193 +  int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
  1.2194 +  bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
  1.2195 +  bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
  1.2196 +
  1.2197 +  // Create a new nsCookie and assign the data.
  1.2198 +  return nsCookie::Create(name, value, host, path,
  1.2199 +                          expiry,
  1.2200 +                          lastAccessed,
  1.2201 +                          creationTime,
  1.2202 +                          false,
  1.2203 +                          isSecure,
  1.2204 +                          isHttpOnly);
  1.2205 +}
  1.2206 +
  1.2207 +void
  1.2208 +nsCookieService::AsyncReadComplete()
  1.2209 +{
  1.2210 +  // We may be in the private browsing DB state, with a pending read on the
  1.2211 +  // default DB state. (This would occur if we started up in private browsing
  1.2212 +  // mode.) As long as we do all our operations on the default state, we're OK.
  1.2213 +  NS_ASSERTION(mDefaultDBState, "no default DBState");
  1.2214 +  NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  1.2215 +  NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  1.2216 +
  1.2217 +  // Merge the data read on the background thread with the data synchronously
  1.2218 +  // read on the main thread. Note that transactions on the cookie table may
  1.2219 +  // have occurred on the main thread since, making the background data stale.
  1.2220 +  for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
  1.2221 +    const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
  1.2222 +
  1.2223 +    // Tiebreak: if the given base domain has already been read in, ignore
  1.2224 +    // the background data. Note that readSet may contain domains that were
  1.2225 +    // queried but found not to be in the db -- that's harmless.
  1.2226 +    if (mDefaultDBState->readSet.GetEntry(tuple.key))
  1.2227 +      continue;
  1.2228 +
  1.2229 +    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
  1.2230 +  }
  1.2231 +
  1.2232 +  mDefaultDBState->stmtReadDomain = nullptr;
  1.2233 +  mDefaultDBState->pendingRead = nullptr;
  1.2234 +  mDefaultDBState->readListener = nullptr;
  1.2235 +  mDefaultDBState->syncConn = nullptr;
  1.2236 +  mDefaultDBState->hostArray.Clear();
  1.2237 +  mDefaultDBState->readSet.Clear();
  1.2238 +
  1.2239 +  COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
  1.2240 +                                  mDefaultDBState->cookieCount));
  1.2241 +
  1.2242 +  mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
  1.2243 +}
  1.2244 +
  1.2245 +void
  1.2246 +nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
  1.2247 +{
  1.2248 +  // We may be in the private browsing DB state, with a pending read on the
  1.2249 +  // default DB state. (This would occur if we started up in private browsing
  1.2250 +  // mode.) As long as we do all our operations on the default state, we're OK.
  1.2251 +  NS_ASSERTION(mDefaultDBState, "no default DBState");
  1.2252 +  NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  1.2253 +  NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  1.2254 +
  1.2255 +  // Cancel the pending read, kill the read listener, and empty the array
  1.2256 +  // of data already read in on the background thread.
  1.2257 +  mDefaultDBState->readListener->Cancel();
  1.2258 +  DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
  1.2259 +  NS_ASSERT_SUCCESS(rv);
  1.2260 +
  1.2261 +  mDefaultDBState->stmtReadDomain = nullptr;
  1.2262 +  mDefaultDBState->pendingRead = nullptr;
  1.2263 +  mDefaultDBState->readListener = nullptr;
  1.2264 +  mDefaultDBState->hostArray.Clear();
  1.2265 +
  1.2266 +  // Only clear the 'readSet' table if we no longer need to know what set of
  1.2267 +  // data is already accounted for.
  1.2268 +  if (aPurgeReadSet)
  1.2269 +    mDefaultDBState->readSet.Clear();
  1.2270 +}
  1.2271 +
  1.2272 +void
  1.2273 +nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
  1.2274 +{
  1.2275 +  NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  1.2276 +    "not in default db state");
  1.2277 +
  1.2278 +  // Fast path 1: nothing to read, or we've already finished reading.
  1.2279 +  if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  1.2280 +    return;
  1.2281 +
  1.2282 +  // Fast path 2: already read in this particular domain.
  1.2283 +  if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
  1.2284 +    return;
  1.2285 +
  1.2286 +  // Read in the data synchronously.
  1.2287 +  // see IDX_NAME, etc. for parameter indexes
  1.2288 +  nsresult rv;
  1.2289 +  if (!mDefaultDBState->stmtReadDomain) {
  1.2290 +    // Cache the statement, since it's likely to be used again.
  1.2291 +    rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  1.2292 +      "SELECT "
  1.2293 +        "name, "
  1.2294 +        "value, "
  1.2295 +        "host, "
  1.2296 +        "path, "
  1.2297 +        "expiry, "
  1.2298 +        "lastAccessed, "
  1.2299 +        "creationTime, "
  1.2300 +        "isSecure, "
  1.2301 +        "isHttpOnly "
  1.2302 +      "FROM moz_cookies "
  1.2303 +      "WHERE baseDomain = :baseDomain "
  1.2304 +      "  AND appId = :appId "
  1.2305 +      "  AND inBrowserElement = :inBrowserElement"),
  1.2306 +      getter_AddRefs(mDefaultDBState->stmtReadDomain));
  1.2307 +
  1.2308 +    if (NS_FAILED(rv)) {
  1.2309 +      // Recreate the database.
  1.2310 +      COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2311 +        ("EnsureReadDomain(): corruption detected when creating statement "
  1.2312 +         "with rv 0x%x", rv));
  1.2313 +      HandleCorruptDB(mDefaultDBState);
  1.2314 +      return;
  1.2315 +    }
  1.2316 +  }
  1.2317 +
  1.2318 +  NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
  1.2319 +
  1.2320 +  mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
  1.2321 +
  1.2322 +  rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
  1.2323 +    NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
  1.2324 +  NS_ASSERT_SUCCESS(rv);
  1.2325 +  rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  1.2326 +    NS_LITERAL_CSTRING("appId"), aKey.mAppId);
  1.2327 +  NS_ASSERT_SUCCESS(rv);
  1.2328 +  rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  1.2329 +    NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
  1.2330 +  NS_ASSERT_SUCCESS(rv);
  1.2331 +
  1.2332 +
  1.2333 +  bool hasResult;
  1.2334 +  nsCString name, value, host, path;
  1.2335 +  nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
  1.2336 +  while (1) {
  1.2337 +    rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
  1.2338 +    if (NS_FAILED(rv)) {
  1.2339 +      // Recreate the database.
  1.2340 +      COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2341 +        ("EnsureReadDomain(): corruption detected when reading result "
  1.2342 +         "with rv 0x%x", rv));
  1.2343 +      HandleCorruptDB(mDefaultDBState);
  1.2344 +      return;
  1.2345 +    }
  1.2346 +
  1.2347 +    if (!hasResult)
  1.2348 +      break;
  1.2349 +
  1.2350 +    array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
  1.2351 +  }
  1.2352 +
  1.2353 +  // Add the cookies to the table in a single operation. This makes sure that
  1.2354 +  // either all the cookies get added, or in the case of corruption, none.
  1.2355 +  for (uint32_t i = 0; i < array.Length(); ++i) {
  1.2356 +    AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
  1.2357 +  }
  1.2358 +
  1.2359 +  // Add it to the hashset of read entries, so we don't read it again.
  1.2360 +  mDefaultDBState->readSet.PutEntry(aKey);
  1.2361 +
  1.2362 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2363 +    ("EnsureReadDomain(): %ld cookies read for base domain %s, "
  1.2364 +     " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
  1.2365 +     (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
  1.2366 +}
  1.2367 +
  1.2368 +void
  1.2369 +nsCookieService::EnsureReadComplete()
  1.2370 +{
  1.2371 +  NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  1.2372 +    "not in default db state");
  1.2373 +
  1.2374 +  // Fast path 1: nothing to read, or we've already finished reading.
  1.2375 +  if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  1.2376 +    return;
  1.2377 +
  1.2378 +  // Cancel the pending read, so we don't get any more results.
  1.2379 +  CancelAsyncRead(false);
  1.2380 +
  1.2381 +  // Read in the data synchronously.
  1.2382 +  // see IDX_NAME, etc. for parameter indexes
  1.2383 +  nsCOMPtr<mozIStorageStatement> stmt;
  1.2384 +  nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  1.2385 +    "SELECT "
  1.2386 +      "name, "
  1.2387 +      "value, "
  1.2388 +      "host, "
  1.2389 +      "path, "
  1.2390 +      "expiry, "
  1.2391 +      "lastAccessed, "
  1.2392 +      "creationTime, "
  1.2393 +      "isSecure, "
  1.2394 +      "isHttpOnly, "
  1.2395 +      "baseDomain, "
  1.2396 +      "appId,  "
  1.2397 +      "inBrowserElement "
  1.2398 +    "FROM moz_cookies "
  1.2399 +    "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
  1.2400 +
  1.2401 +  if (NS_FAILED(rv)) {
  1.2402 +    // Recreate the database.
  1.2403 +    COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2404 +      ("EnsureReadComplete(): corruption detected when creating statement "
  1.2405 +       "with rv 0x%x", rv));
  1.2406 +    HandleCorruptDB(mDefaultDBState);
  1.2407 +    return;
  1.2408 +  }
  1.2409 +
  1.2410 +  nsCString baseDomain, name, value, host, path;
  1.2411 +  uint32_t appId;
  1.2412 +  bool inBrowserElement, hasResult;
  1.2413 +  nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
  1.2414 +  while (1) {
  1.2415 +    rv = stmt->ExecuteStep(&hasResult);
  1.2416 +    if (NS_FAILED(rv)) {
  1.2417 +      // Recreate the database.
  1.2418 +      COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2419 +        ("EnsureReadComplete(): corruption detected when reading result "
  1.2420 +         "with rv 0x%x", rv));
  1.2421 +      HandleCorruptDB(mDefaultDBState);
  1.2422 +      return;
  1.2423 +    }
  1.2424 +
  1.2425 +    if (!hasResult)
  1.2426 +      break;
  1.2427 +
  1.2428 +    // Make sure we haven't already read the data.
  1.2429 +    stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
  1.2430 +    appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID));
  1.2431 +    inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM));
  1.2432 +    nsCookieKey key(baseDomain, appId, inBrowserElement);
  1.2433 +    if (mDefaultDBState->readSet.GetEntry(key))
  1.2434 +      continue;
  1.2435 +
  1.2436 +    CookieDomainTuple* tuple = array.AppendElement();
  1.2437 +    tuple->key = key;
  1.2438 +    tuple->cookie = GetCookieFromRow(stmt);
  1.2439 +  }
  1.2440 +
  1.2441 +  // Add the cookies to the table in a single operation. This makes sure that
  1.2442 +  // either all the cookies get added, or in the case of corruption, none.
  1.2443 +  for (uint32_t i = 0; i < array.Length(); ++i) {
  1.2444 +    CookieDomainTuple& tuple = array[i];
  1.2445 +    AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
  1.2446 +      false);
  1.2447 +  }
  1.2448 +
  1.2449 +  mDefaultDBState->syncConn = nullptr;
  1.2450 +  mDefaultDBState->readSet.Clear();
  1.2451 +
  1.2452 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.2453 +    ("EnsureReadComplete(): %ld cookies read", array.Length()));
  1.2454 +}
  1.2455 +
  1.2456 +NS_IMETHODIMP
  1.2457 +nsCookieService::ImportCookies(nsIFile *aCookieFile)
  1.2458 +{
  1.2459 +  if (!mDBState) {
  1.2460 +    NS_WARNING("No DBState! Profile already closed?");
  1.2461 +    return NS_ERROR_NOT_AVAILABLE;
  1.2462 +  }
  1.2463 +
  1.2464 +  // Make sure we're in the default DB state. We don't want people importing
  1.2465 +  // cookies into a private browsing session!
  1.2466 +  if (mDBState != mDefaultDBState) {
  1.2467 +    NS_WARNING("Trying to import cookies in a private browsing session!");
  1.2468 +    return NS_ERROR_NOT_AVAILABLE;
  1.2469 +  }
  1.2470 +
  1.2471 +  nsresult rv;
  1.2472 +  nsCOMPtr<nsIInputStream> fileInputStream;
  1.2473 +  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
  1.2474 +  if (NS_FAILED(rv)) return rv;
  1.2475 +
  1.2476 +  nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
  1.2477 +  if (NS_FAILED(rv)) return rv;
  1.2478 +
  1.2479 +  // First, ensure we've read in everything from the database, if we have one.
  1.2480 +  EnsureReadComplete();
  1.2481 +
  1.2482 +  static const char kTrue[] = "TRUE";
  1.2483 +
  1.2484 +  nsAutoCString buffer, baseDomain;
  1.2485 +  bool isMore = true;
  1.2486 +  int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
  1.2487 +  nsASingleFragmentCString::char_iterator iter;
  1.2488 +  int32_t numInts;
  1.2489 +  int64_t expires;
  1.2490 +  bool isDomain, isHttpOnly = false;
  1.2491 +  uint32_t originalCookieCount = mDefaultDBState->cookieCount;
  1.2492 +
  1.2493 +  int64_t currentTimeInUsec = PR_Now();
  1.2494 +  int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  1.2495 +  // we use lastAccessedCounter to keep cookies in recently-used order,
  1.2496 +  // so we start by initializing to currentTime (somewhat arbitrary)
  1.2497 +  int64_t lastAccessedCounter = currentTimeInUsec;
  1.2498 +
  1.2499 +  /* file format is:
  1.2500 +   *
  1.2501 +   * host \t isDomain \t path \t secure \t expires \t name \t cookie
  1.2502 +   *
  1.2503 +   * if this format isn't respected we move onto the next line in the file.
  1.2504 +   * isDomain is "TRUE" or "FALSE" (default to "FALSE")
  1.2505 +   * isSecure is "TRUE" or "FALSE" (default to "TRUE")
  1.2506 +   * expires is a int64_t integer
  1.2507 +   * note 1: cookie can contain tabs.
  1.2508 +   * note 2: cookies will be stored in order of lastAccessed time:
  1.2509 +   *         most-recently used come first; least-recently-used come last.
  1.2510 +   */
  1.2511 +
  1.2512 +  /*
  1.2513 +   * ...but due to bug 178933, we hide HttpOnly cookies from older code
  1.2514 +   * in a comment, so they don't expose HttpOnly cookies to JS.
  1.2515 +   *
  1.2516 +   * The format for HttpOnly cookies is
  1.2517 +   *
  1.2518 +   * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
  1.2519 +   *
  1.2520 +   */
  1.2521 +
  1.2522 +  // We will likely be adding a bunch of cookies to the DB, so we use async
  1.2523 +  // batching with storage to make this super fast.
  1.2524 +  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1.2525 +  if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
  1.2526 +    mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.2527 +  }
  1.2528 +
  1.2529 +  while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
  1.2530 +    if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
  1.2531 +      isHttpOnly = true;
  1.2532 +      hostIndex = sizeof(kHttpOnlyPrefix) - 1;
  1.2533 +    } else if (buffer.IsEmpty() || buffer.First() == '#') {
  1.2534 +      continue;
  1.2535 +    } else {
  1.2536 +      isHttpOnly = false;
  1.2537 +      hostIndex = 0;
  1.2538 +    }
  1.2539 +
  1.2540 +    // this is a cheap, cheesy way of parsing a tab-delimited line into
  1.2541 +    // string indexes, which can be lopped off into substrings. just for
  1.2542 +    // purposes of obfuscation, it also checks that each token was found.
  1.2543 +    // todo: use iterators?
  1.2544 +    if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
  1.2545 +        (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
  1.2546 +        (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
  1.2547 +        (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
  1.2548 +        (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
  1.2549 +        (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
  1.2550 +      continue;
  1.2551 +    }
  1.2552 +
  1.2553 +    // check the expirytime first - if it's expired, ignore
  1.2554 +    // nullstomp the trailing tab, to avoid copying the string
  1.2555 +    buffer.BeginWriting(iter);
  1.2556 +    *(iter += nameIndex - 1) = char(0);
  1.2557 +    numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
  1.2558 +    if (numInts != 1 || expires < currentTime) {
  1.2559 +      continue;
  1.2560 +    }
  1.2561 +
  1.2562 +    isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
  1.2563 +    const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
  1.2564 +    // check for bad legacy cookies (domain not starting with a dot, or containing a port),
  1.2565 +    // and discard
  1.2566 +    if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
  1.2567 +        host.FindChar(':') != kNotFound) {
  1.2568 +      continue;
  1.2569 +    }
  1.2570 +
  1.2571 +    // compute the baseDomain from the host
  1.2572 +    rv = GetBaseDomainFromHost(host, baseDomain);
  1.2573 +    if (NS_FAILED(rv))
  1.2574 +      continue;
  1.2575 +
  1.2576 +    // pre-existing cookies have appId=0, inBrowser=false
  1.2577 +    nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  1.2578 +
  1.2579 +    // Create a new nsCookie and assign the data. We don't know the cookie
  1.2580 +    // creation time, so just use the current time to generate a unique one.
  1.2581 +    nsRefPtr<nsCookie> newCookie =
  1.2582 +      nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
  1.2583 +                       Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
  1.2584 +                       host,
  1.2585 +                       Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
  1.2586 +                       expires,
  1.2587 +                       lastAccessedCounter,
  1.2588 +                       nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  1.2589 +                       false,
  1.2590 +                       Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
  1.2591 +                       isHttpOnly);
  1.2592 +    if (!newCookie) {
  1.2593 +      return NS_ERROR_OUT_OF_MEMORY;
  1.2594 +    }
  1.2595 +    
  1.2596 +    // trick: preserve the most-recently-used cookie ordering,
  1.2597 +    // by successively decrementing the lastAccessed time
  1.2598 +    lastAccessedCounter--;
  1.2599 +
  1.2600 +    if (originalCookieCount == 0) {
  1.2601 +      AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
  1.2602 +    }
  1.2603 +    else {
  1.2604 +      AddInternal(key, newCookie, currentTimeInUsec,
  1.2605 +                  nullptr, nullptr, true);
  1.2606 +    }
  1.2607 +  }
  1.2608 +
  1.2609 +  // If we need to write to disk, do so now.
  1.2610 +  if (paramsArray) {
  1.2611 +    uint32_t length;
  1.2612 +    paramsArray->GetLength(&length);
  1.2613 +    if (length) {
  1.2614 +      rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
  1.2615 +      NS_ASSERT_SUCCESS(rv);
  1.2616 +      nsCOMPtr<mozIStoragePendingStatement> handle;
  1.2617 +      rv = mDefaultDBState->stmtInsert->ExecuteAsync(
  1.2618 +        mDefaultDBState->insertListener, getter_AddRefs(handle));
  1.2619 +      NS_ASSERT_SUCCESS(rv);
  1.2620 +    }
  1.2621 +  }
  1.2622 +
  1.2623 +
  1.2624 +  COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
  1.2625 +    mDefaultDBState->cookieCount));
  1.2626 +
  1.2627 +  return NS_OK;
  1.2628 +}
  1.2629 +
  1.2630 +/******************************************************************************
  1.2631 + * nsCookieService impl:
  1.2632 + * private GetCookie/SetCookie helpers
  1.2633 + ******************************************************************************/
  1.2634 +
  1.2635 +// helper function for GetCookieList
  1.2636 +static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
  1.2637 +
  1.2638 +// Comparator class for sorting cookies before sending to a server.
  1.2639 +class CompareCookiesForSending
  1.2640 +{
  1.2641 +public:
  1.2642 +  bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  1.2643 +  {
  1.2644 +    return aCookie1->CreationTime() == aCookie2->CreationTime() &&
  1.2645 +           aCookie2->Path().Length() == aCookie1->Path().Length();
  1.2646 +  }
  1.2647 +
  1.2648 +  bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  1.2649 +  {
  1.2650 +    // compare by cookie path length in accordance with RFC2109
  1.2651 +    int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
  1.2652 +    if (result != 0)
  1.2653 +      return result < 0;
  1.2654 +
  1.2655 +    // when path lengths match, older cookies should be listed first.  this is
  1.2656 +    // required for backwards compatibility since some websites erroneously
  1.2657 +    // depend on receiving cookies in the order in which they were sent to the
  1.2658 +    // browser!  see bug 236772.
  1.2659 +    return aCookie1->CreationTime() < aCookie2->CreationTime();
  1.2660 +  }
  1.2661 +};
  1.2662 +
  1.2663 +void
  1.2664 +nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
  1.2665 +                                         bool aIsForeign,
  1.2666 +                                         bool aHttpBound,
  1.2667 +                                         uint32_t aAppId,
  1.2668 +                                         bool aInBrowserElement,
  1.2669 +                                         bool aIsPrivate,
  1.2670 +                                         nsCString &aCookieString)
  1.2671 +{
  1.2672 +  NS_ASSERTION(aHostURI, "null host!");
  1.2673 +
  1.2674 +  if (!mDBState) {
  1.2675 +    NS_WARNING("No DBState! Profile already closed?");
  1.2676 +    return;
  1.2677 +  }
  1.2678 +
  1.2679 +  AutoRestore<DBState*> savePrevDBState(mDBState);
  1.2680 +  mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  1.2681 +
  1.2682 +  // get the base domain, host, and path from the URI.
  1.2683 +  // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
  1.2684 +  // file:// URI's (i.e. with an empty host) are allowed, but any other
  1.2685 +  // scheme must have a non-empty host. A trailing dot in the host
  1.2686 +  // is acceptable.
  1.2687 +  bool requireHostMatch;
  1.2688 +  nsAutoCString baseDomain, hostFromURI, pathFromURI;
  1.2689 +  nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  1.2690 +  if (NS_SUCCEEDED(rv))
  1.2691 +    rv = aHostURI->GetAsciiHost(hostFromURI);
  1.2692 +  if (NS_SUCCEEDED(rv))
  1.2693 +    rv = aHostURI->GetPath(pathFromURI);
  1.2694 +  if (NS_FAILED(rv)) {
  1.2695 +    COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
  1.2696 +    return;
  1.2697 +  }
  1.2698 +
  1.2699 +  // check default prefs
  1.2700 +  CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  1.2701 +                                         nullptr);
  1.2702 +  // for GetCookie(), we don't fire rejection notifications.
  1.2703 +  switch (cookieStatus) {
  1.2704 +  case STATUS_REJECTED:
  1.2705 +  case STATUS_REJECTED_WITH_ERROR:
  1.2706 +    return;
  1.2707 +  default:
  1.2708 +    break;
  1.2709 +  }
  1.2710 +
  1.2711 +  // check if aHostURI is using an https secure protocol.
  1.2712 +  // if it isn't, then we can't send a secure cookie over the connection.
  1.2713 +  // if SchemeIs fails, assume an insecure connection, to be on the safe side
  1.2714 +  bool isSecure;
  1.2715 +  if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
  1.2716 +    isSecure = false;
  1.2717 +  }
  1.2718 +
  1.2719 +  nsCookie *cookie;
  1.2720 +  nsAutoTArray<nsCookie*, 8> foundCookieList;
  1.2721 +  int64_t currentTimeInUsec = PR_Now();
  1.2722 +  int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  1.2723 +  bool stale = false;
  1.2724 +
  1.2725 +  nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  1.2726 +  EnsureReadDomain(key);
  1.2727 +
  1.2728 +  // perform the hash lookup
  1.2729 +  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  1.2730 +  if (!entry)
  1.2731 +    return;
  1.2732 +
  1.2733 +  // iterate the cookies!
  1.2734 +  const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  1.2735 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.2736 +    cookie = cookies[i];
  1.2737 +
  1.2738 +    // check the host, since the base domain lookup is conservative.
  1.2739 +    // first, check for an exact host or domain cookie match, e.g. "google.com"
  1.2740 +    // or ".google.com"; second a subdomain match, e.g.
  1.2741 +    // host = "mail.google.com", cookie domain = ".google.com".
  1.2742 +    if (cookie->RawHost() != hostFromURI &&
  1.2743 +        !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
  1.2744 +      continue;
  1.2745 +
  1.2746 +    // if the cookie is secure and the host scheme isn't, we can't send it
  1.2747 +    if (cookie->IsSecure() && !isSecure)
  1.2748 +      continue;
  1.2749 +
  1.2750 +    // if the cookie is httpOnly and it's not going directly to the HTTP
  1.2751 +    // connection, don't send it
  1.2752 +    if (cookie->IsHttpOnly() && !aHttpBound)
  1.2753 +      continue;
  1.2754 +
  1.2755 +    // calculate cookie path length, excluding trailing '/'
  1.2756 +    uint32_t cookiePathLen = cookie->Path().Length();
  1.2757 +    if (cookiePathLen > 0 && cookie->Path().Last() == '/')
  1.2758 +      --cookiePathLen;
  1.2759 +
  1.2760 +    // if the nsIURI path is shorter than the cookie path, don't send it back
  1.2761 +    if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
  1.2762 +      continue;
  1.2763 +
  1.2764 +    if (pathFromURI.Length() > cookiePathLen &&
  1.2765 +        !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
  1.2766 +      /*
  1.2767 +       * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
  1.2768 +       * '/' is the "standard" case; the '?' test allows a site at host/abc?def
  1.2769 +       * to receive a cookie that has a path attribute of abc.  this seems
  1.2770 +       * strange but at least one major site (citibank, bug 156725) depends
  1.2771 +       * on it.  The test for # and ; are put in to proactively avoid problems
  1.2772 +       * with other sites - these are the only other chars allowed in the path.
  1.2773 +       */
  1.2774 +      continue;
  1.2775 +    }
  1.2776 +
  1.2777 +    // check if the cookie has expired
  1.2778 +    if (cookie->Expiry() <= currentTime) {
  1.2779 +      continue;
  1.2780 +    }
  1.2781 +
  1.2782 +    // all checks passed - add to list and check if lastAccessed stamp needs updating
  1.2783 +    foundCookieList.AppendElement(cookie);
  1.2784 +    if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  1.2785 +      stale = true;
  1.2786 +  }
  1.2787 +
  1.2788 +  int32_t count = foundCookieList.Length();
  1.2789 +  if (count == 0)
  1.2790 +    return;
  1.2791 +
  1.2792 +  // update lastAccessed timestamps. we only do this if the timestamp is stale
  1.2793 +  // by a certain amount, to avoid thrashing the db during pageload.
  1.2794 +  if (stale) {
  1.2795 +    // Create an array of parameters to bind to our update statement. Batching
  1.2796 +    // is OK here since we're updating cookies with no interleaved operations.
  1.2797 +    nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1.2798 +    mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
  1.2799 +    if (mDBState->dbConn) {
  1.2800 +      stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.2801 +    }
  1.2802 +
  1.2803 +    for (int32_t i = 0; i < count; ++i) {
  1.2804 +      cookie = foundCookieList.ElementAt(i);
  1.2805 +
  1.2806 +      if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  1.2807 +        UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
  1.2808 +    }
  1.2809 +    // Update the database now if necessary.
  1.2810 +    if (paramsArray) {
  1.2811 +      uint32_t length;
  1.2812 +      paramsArray->GetLength(&length);
  1.2813 +      if (length) {
  1.2814 +        DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1.2815 +        NS_ASSERT_SUCCESS(rv);
  1.2816 +        nsCOMPtr<mozIStoragePendingStatement> handle;
  1.2817 +        rv = stmt->ExecuteAsync(mDBState->updateListener,
  1.2818 +          getter_AddRefs(handle));
  1.2819 +        NS_ASSERT_SUCCESS(rv);
  1.2820 +      }
  1.2821 +    }
  1.2822 +  }
  1.2823 +
  1.2824 +  // return cookies in order of path length; longest to shortest.
  1.2825 +  // this is required per RFC2109.  if cookies match in length,
  1.2826 +  // then sort by creation time (see bug 236772).
  1.2827 +  foundCookieList.Sort(CompareCookiesForSending());
  1.2828 +
  1.2829 +  for (int32_t i = 0; i < count; ++i) {
  1.2830 +    cookie = foundCookieList.ElementAt(i);
  1.2831 +
  1.2832 +    // check if we have anything to write
  1.2833 +    if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
  1.2834 +      // if we've already added a cookie to the return list, append a "; " so
  1.2835 +      // that subsequent cookies are delimited in the final list.
  1.2836 +      if (!aCookieString.IsEmpty()) {
  1.2837 +        aCookieString.AppendLiteral("; ");
  1.2838 +      }
  1.2839 +
  1.2840 +      if (!cookie->Name().IsEmpty()) {
  1.2841 +        // we have a name and value - write both
  1.2842 +        aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
  1.2843 +      } else {
  1.2844 +        // just write value
  1.2845 +        aCookieString += cookie->Value();
  1.2846 +      }
  1.2847 +    }
  1.2848 +  }
  1.2849 +
  1.2850 +  if (!aCookieString.IsEmpty())
  1.2851 +    COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
  1.2852 +}
  1.2853 +
  1.2854 +// processes a single cookie, and returns true if there are more cookies
  1.2855 +// to be processed
  1.2856 +bool
  1.2857 +nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
  1.2858 +                                   const nsCookieKey             &aKey,
  1.2859 +                                   bool                           aRequireHostMatch,
  1.2860 +                                   CookieStatus                   aStatus,
  1.2861 +                                   nsDependentCString            &aCookieHeader,
  1.2862 +                                   int64_t                        aServerTime,
  1.2863 +                                   bool                           aFromHttp,
  1.2864 +                                   nsIChannel                    *aChannel)
  1.2865 +{
  1.2866 +  NS_ASSERTION(aHostURI, "null host!");
  1.2867 +
  1.2868 +  // create a stack-based nsCookieAttributes, to store all the
  1.2869 +  // attributes parsed from the cookie
  1.2870 +  nsCookieAttributes cookieAttributes;
  1.2871 +
  1.2872 +  // init expiryTime such that session cookies won't prematurely expire
  1.2873 +  cookieAttributes.expiryTime = INT64_MAX;
  1.2874 +
  1.2875 +  // aCookieHeader is an in/out param to point to the next cookie, if
  1.2876 +  // there is one. Save the present value for logging purposes
  1.2877 +  nsDependentCString savedCookieHeader(aCookieHeader);
  1.2878 +
  1.2879 +  // newCookie says whether there are multiple cookies in the header;
  1.2880 +  // so we can handle them separately.
  1.2881 +  bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
  1.2882 +
  1.2883 +  int64_t currentTimeInUsec = PR_Now();
  1.2884 +
  1.2885 +  // calculate expiry time of cookie.
  1.2886 +  cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
  1.2887 +                                         currentTimeInUsec / PR_USEC_PER_SEC);
  1.2888 +  if (aStatus == STATUS_ACCEPT_SESSION) {
  1.2889 +    // force lifetime to session. note that the expiration time, if set above,
  1.2890 +    // will still apply.
  1.2891 +    cookieAttributes.isSession = true;
  1.2892 +  }
  1.2893 +
  1.2894 +  // reject cookie if it's over the size limit, per RFC2109
  1.2895 +  if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
  1.2896 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
  1.2897 +    return newCookie;
  1.2898 +  }
  1.2899 +
  1.2900 +  if (cookieAttributes.name.FindChar('\t') != kNotFound) {
  1.2901 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
  1.2902 +    return newCookie;
  1.2903 +  }
  1.2904 +
  1.2905 +  // domain & path checks
  1.2906 +  if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
  1.2907 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
  1.2908 +    return newCookie;
  1.2909 +  }
  1.2910 +  if (!CheckPath(cookieAttributes, aHostURI)) {
  1.2911 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
  1.2912 +    return newCookie;
  1.2913 +  }
  1.2914 +
  1.2915 +  // create a new nsCookie and copy attributes
  1.2916 +  nsRefPtr<nsCookie> cookie =
  1.2917 +    nsCookie::Create(cookieAttributes.name,
  1.2918 +                     cookieAttributes.value,
  1.2919 +                     cookieAttributes.host,
  1.2920 +                     cookieAttributes.path,
  1.2921 +                     cookieAttributes.expiryTime,
  1.2922 +                     currentTimeInUsec,
  1.2923 +                     nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  1.2924 +                     cookieAttributes.isSession,
  1.2925 +                     cookieAttributes.isSecure,
  1.2926 +                     cookieAttributes.isHttpOnly);
  1.2927 +  if (!cookie)
  1.2928 +    return newCookie;
  1.2929 +
  1.2930 +  // check permissions from site permission list, or ask the user,
  1.2931 +  // to determine if we can set the cookie
  1.2932 +  if (mPermissionService) {
  1.2933 +    bool permission;
  1.2934 +    mPermissionService->CanSetCookie(aHostURI,
  1.2935 +                                     aChannel,
  1.2936 +                                     static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
  1.2937 +                                     &cookieAttributes.isSession,
  1.2938 +                                     &cookieAttributes.expiryTime,
  1.2939 +                                     &permission);
  1.2940 +    if (!permission) {
  1.2941 +      COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
  1.2942 +      NotifyRejected(aHostURI);
  1.2943 +      return newCookie;
  1.2944 +    }
  1.2945 +
  1.2946 +    // update isSession and expiry attributes, in case they changed
  1.2947 +    cookie->SetIsSession(cookieAttributes.isSession);
  1.2948 +    cookie->SetExpiry(cookieAttributes.expiryTime);
  1.2949 +  }
  1.2950 +
  1.2951 +  // add the cookie to the list. AddInternal() takes care of logging.
  1.2952 +  // we get the current time again here, since it may have changed during prompting
  1.2953 +  AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
  1.2954 +              aFromHttp);
  1.2955 +  return newCookie;
  1.2956 +}
  1.2957 +
  1.2958 +// this is a backend function for adding a cookie to the list, via SetCookie.
  1.2959 +// also used in the cookie manager, for profile migration from IE.
  1.2960 +// it either replaces an existing cookie; or adds the cookie to the hashtable,
  1.2961 +// and deletes a cookie (if maximum number of cookies has been
  1.2962 +// reached). also performs list maintenance by removing expired cookies.
  1.2963 +void
  1.2964 +nsCookieService::AddInternal(const nsCookieKey             &aKey,
  1.2965 +                             nsCookie                      *aCookie,
  1.2966 +                             int64_t                        aCurrentTimeInUsec,
  1.2967 +                             nsIURI                        *aHostURI,
  1.2968 +                             const char                    *aCookieHeader,
  1.2969 +                             bool                           aFromHttp)
  1.2970 +{
  1.2971 +  int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
  1.2972 +
  1.2973 +  // if the new cookie is httponly, make sure we're not coming from script
  1.2974 +  if (!aFromHttp && aCookie->IsHttpOnly()) {
  1.2975 +    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.2976 +      "cookie is httponly; coming from script");
  1.2977 +    return;
  1.2978 +  }
  1.2979 +
  1.2980 +  nsListIter matchIter;
  1.2981 +  bool foundCookie = FindCookie(aKey, aCookie->Host(),
  1.2982 +    aCookie->Name(), aCookie->Path(), matchIter);
  1.2983 +
  1.2984 +  nsRefPtr<nsCookie> oldCookie;
  1.2985 +  nsCOMPtr<nsIArray> purgedList;
  1.2986 +  if (foundCookie) {
  1.2987 +    oldCookie = matchIter.Cookie();
  1.2988 +
  1.2989 +    // Check if the old cookie is stale (i.e. has already expired). If so, we
  1.2990 +    // need to be careful about the semantics of removing it and adding the new
  1.2991 +    // cookie: we want the behavior wrt adding the new cookie to be the same as
  1.2992 +    // if it didn't exist, but we still want to fire a removal notification.
  1.2993 +    if (oldCookie->Expiry() <= currentTime) {
  1.2994 +      if (aCookie->Expiry() <= currentTime) {
  1.2995 +        // The new cookie has expired and the old one is stale. Nothing to do.
  1.2996 +        COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.2997 +          "cookie has already expired");
  1.2998 +        return;
  1.2999 +      }
  1.3000 +
  1.3001 +      // Remove the stale cookie. We save notification for later, once all list
  1.3002 +      // modifications are complete.
  1.3003 +      RemoveCookieFromList(matchIter);
  1.3004 +      COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.3005 +        "stale cookie was purged");
  1.3006 +      purgedList = CreatePurgeList(oldCookie);
  1.3007 +
  1.3008 +      // We've done all we need to wrt removing and notifying the stale cookie.
  1.3009 +      // From here on out, we pretend pretend it didn't exist, so that we
  1.3010 +      // preserve expected notification semantics when adding the new cookie.
  1.3011 +      foundCookie = false;
  1.3012 +
  1.3013 +    } else {
  1.3014 +      // If the old cookie is httponly, make sure we're not coming from script.
  1.3015 +      if (!aFromHttp && oldCookie->IsHttpOnly()) {
  1.3016 +        COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.3017 +          "previously stored cookie is httponly; coming from script");
  1.3018 +        return;
  1.3019 +      }
  1.3020 +
  1.3021 +      // Remove the old cookie.
  1.3022 +      RemoveCookieFromList(matchIter);
  1.3023 +
  1.3024 +      // If the new cookie has expired -- i.e. the intent was simply to delete
  1.3025 +      // the old cookie -- then we're done.
  1.3026 +      if (aCookie->Expiry() <= currentTime) {
  1.3027 +        COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.3028 +          "previously stored cookie was deleted");
  1.3029 +        NotifyChanged(oldCookie, MOZ_UTF16("deleted"));
  1.3030 +        return;
  1.3031 +      }
  1.3032 +
  1.3033 +      // Preserve creation time of cookie for ordering purposes.
  1.3034 +      aCookie->SetCreationTime(oldCookie->CreationTime());
  1.3035 +    }
  1.3036 +
  1.3037 +  } else {
  1.3038 +    // check if cookie has already expired
  1.3039 +    if (aCookie->Expiry() <= currentTime) {
  1.3040 +      COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  1.3041 +        "cookie has already expired");
  1.3042 +      return;
  1.3043 +    }
  1.3044 +
  1.3045 +    // check if we have to delete an old cookie.
  1.3046 +    nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  1.3047 +    if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
  1.3048 +      nsListIter iter;
  1.3049 +      FindStaleCookie(entry, currentTime, iter);
  1.3050 +      oldCookie = iter.Cookie();
  1.3051 +
  1.3052 +      // remove the oldest cookie from the domain
  1.3053 +      RemoveCookieFromList(iter);
  1.3054 +      COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
  1.3055 +      purgedList = CreatePurgeList(oldCookie);
  1.3056 +
  1.3057 +    } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
  1.3058 +      int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
  1.3059 +      int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
  1.3060 +      if (maxAge >= purgeAge) {
  1.3061 +        // we're over both size and age limits by 10%; time to purge the table!
  1.3062 +        // do this by:
  1.3063 +        // 1) removing expired cookies;
  1.3064 +        // 2) evicting the balance of old cookies until we reach the size limit.
  1.3065 +        // note that the cookieOldestTime indicator can be pessimistic - if it's
  1.3066 +        // older than the actual oldest cookie, we'll just purge more eagerly.
  1.3067 +        purgedList = PurgeCookies(aCurrentTimeInUsec);
  1.3068 +      }
  1.3069 +    }
  1.3070 +  }
  1.3071 +
  1.3072 +  // Add the cookie to the db. We do not supply a params array for batching
  1.3073 +  // because this might result in removals and additions being out of order.
  1.3074 +  AddCookieToList(aKey, aCookie, mDBState, nullptr);
  1.3075 +  COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
  1.3076 +
  1.3077 +  // Now that list mutations are complete, notify observers. We do it here
  1.3078 +  // because observers may themselves attempt to mutate the list.
  1.3079 +  if (purgedList) {
  1.3080 +    NotifyChanged(purgedList, MOZ_UTF16("batch-deleted"));
  1.3081 +  }
  1.3082 +
  1.3083 +  NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
  1.3084 +                                     : MOZ_UTF16("added"));
  1.3085 +}
  1.3086 +
  1.3087 +/******************************************************************************
  1.3088 + * nsCookieService impl:
  1.3089 + * private cookie header parsing functions
  1.3090 + ******************************************************************************/
  1.3091 +
  1.3092 +// The following comment block elucidates the function of ParseAttributes.
  1.3093 +/******************************************************************************
  1.3094 + ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
  1.3095 + ** please note: this BNF deviates from both specifications, and reflects this
  1.3096 + ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
  1.3097 +
  1.3098 + ** Differences from RFC2109/2616 and explanations:
  1.3099 +    1. implied *LWS
  1.3100 +         The grammar described by this specification is word-based. Except
  1.3101 +         where noted otherwise, linear white space (<LWS>) can be included
  1.3102 +         between any two adjacent words (token or quoted-string), and
  1.3103 +         between adjacent words and separators, without changing the
  1.3104 +         interpretation of a field.
  1.3105 +       <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
  1.3106 +
  1.3107 +    2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
  1.3108 +       common use inside values.
  1.3109 +
  1.3110 +    3. tokens and values have looser restrictions on allowed characters than
  1.3111 +       spec. This is also due to certain characters being in common use inside
  1.3112 +       values. We allow only '=' to separate token/value pairs, and ';' to
  1.3113 +       terminate tokens or values. <LWS> is allowed within tokens and values
  1.3114 +       (see bug 206022).
  1.3115 +
  1.3116 +    4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
  1.3117 +       reject control chars or non-ASCII chars. This is erring on the loose
  1.3118 +       side, since there's probably no good reason to enforce this strictness.
  1.3119 +
  1.3120 +    5. cookie <NAME> is optional, where spec requires it. This is a fairly
  1.3121 +       trivial case, but allows the flexibility of setting only a cookie <VALUE>
  1.3122 +       with a blank <NAME> and is required by some sites (see bug 169091).
  1.3123 +       
  1.3124 +    6. Attribute "HttpOnly", not covered in the RFCs, is supported
  1.3125 +       (see bug 178993).
  1.3126 +
  1.3127 + ** Begin BNF:
  1.3128 +    token         = 1*<any allowed-chars except separators>
  1.3129 +    value         = 1*<any allowed-chars except value-sep>
  1.3130 +    separators    = ";" | "="
  1.3131 +    value-sep     = ";"
  1.3132 +    cookie-sep    = CR | LF
  1.3133 +    allowed-chars = <any OCTET except NUL or cookie-sep>
  1.3134 +    OCTET         = <any 8-bit sequence of data>
  1.3135 +    LWS           = SP | HT
  1.3136 +    NUL           = <US-ASCII NUL, null control character (0)>
  1.3137 +    CR            = <US-ASCII CR, carriage return (13)>
  1.3138 +    LF            = <US-ASCII LF, linefeed (10)>
  1.3139 +    SP            = <US-ASCII SP, space (32)>
  1.3140 +    HT            = <US-ASCII HT, horizontal-tab (9)>
  1.3141 +
  1.3142 +    set-cookie    = "Set-Cookie:" cookies
  1.3143 +    cookies       = cookie *( cookie-sep cookie )
  1.3144 +    cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
  1.3145 +    NAME          = token                                ; cookie name
  1.3146 +    VALUE         = value                                ; cookie value
  1.3147 +    cookie-av     = token ["=" value]
  1.3148 +
  1.3149 +    valid values for cookie-av (checked post-parsing) are:
  1.3150 +    cookie-av     = "Path"    "=" value
  1.3151 +                  | "Domain"  "=" value
  1.3152 +                  | "Expires" "=" value
  1.3153 +                  | "Max-Age" "=" value
  1.3154 +                  | "Comment" "=" value
  1.3155 +                  | "Version" "=" value
  1.3156 +                  | "Secure"
  1.3157 +                  | "HttpOnly"
  1.3158 +
  1.3159 +******************************************************************************/
  1.3160 +
  1.3161 +// helper functions for GetTokenValue
  1.3162 +static inline bool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
  1.3163 +static inline bool isterminator     (char c) { return c == '\n' || c == '\r'; }
  1.3164 +static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
  1.3165 +static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
  1.3166 +
  1.3167 +// Parse a single token/value pair.
  1.3168 +// Returns true if a cookie terminator is found, so caller can parse new cookie.
  1.3169 +bool
  1.3170 +nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
  1.3171 +                               nsASingleFragmentCString::const_char_iterator &aEndIter,
  1.3172 +                               nsDependentCSubstring                         &aTokenString,
  1.3173 +                               nsDependentCSubstring                         &aTokenValue,
  1.3174 +                               bool                                          &aEqualsFound)
  1.3175 +{
  1.3176 +  nsASingleFragmentCString::const_char_iterator start, lastSpace;
  1.3177 +  // initialize value string to clear garbage
  1.3178 +  aTokenValue.Rebind(aIter, aIter);
  1.3179 +
  1.3180 +  // find <token>, including any <LWS> between the end-of-token and the
  1.3181 +  // token separator. we'll remove trailing <LWS> next
  1.3182 +  while (aIter != aEndIter && iswhitespace(*aIter))
  1.3183 +    ++aIter;
  1.3184 +  start = aIter;
  1.3185 +  while (aIter != aEndIter && !istokenseparator(*aIter))
  1.3186 +    ++aIter;
  1.3187 +
  1.3188 +  // remove trailing <LWS>; first check we're not at the beginning
  1.3189 +  lastSpace = aIter;
  1.3190 +  if (lastSpace != start) {
  1.3191 +    while (--lastSpace != start && iswhitespace(*lastSpace))
  1.3192 +      continue;
  1.3193 +    ++lastSpace;
  1.3194 +  }
  1.3195 +  aTokenString.Rebind(start, lastSpace);
  1.3196 +
  1.3197 +  aEqualsFound = (*aIter == '=');
  1.3198 +  if (aEqualsFound) {
  1.3199 +    // find <value>
  1.3200 +    while (++aIter != aEndIter && iswhitespace(*aIter))
  1.3201 +      continue;
  1.3202 +
  1.3203 +    start = aIter;
  1.3204 +
  1.3205 +    // process <token>
  1.3206 +    // just look for ';' to terminate ('=' allowed)
  1.3207 +    while (aIter != aEndIter && !isvalueseparator(*aIter))
  1.3208 +      ++aIter;
  1.3209 +
  1.3210 +    // remove trailing <LWS>; first check we're not at the beginning
  1.3211 +    if (aIter != start) {
  1.3212 +      lastSpace = aIter;
  1.3213 +      while (--lastSpace != start && iswhitespace(*lastSpace))
  1.3214 +        continue;
  1.3215 +      aTokenValue.Rebind(start, ++lastSpace);
  1.3216 +    }
  1.3217 +  }
  1.3218 +
  1.3219 +  // aIter is on ';', or terminator, or EOS
  1.3220 +  if (aIter != aEndIter) {
  1.3221 +    // if on terminator, increment past & return true to process new cookie
  1.3222 +    if (isterminator(*aIter)) {
  1.3223 +      ++aIter;
  1.3224 +      return true;
  1.3225 +    }
  1.3226 +    // fall-through: aIter is on ';', increment and return false
  1.3227 +    ++aIter;
  1.3228 +  }
  1.3229 +  return false;
  1.3230 +}
  1.3231 +
  1.3232 +// Parses attributes from cookie header. expires/max-age attributes aren't folded into the
  1.3233 +// cookie struct here, because we don't know which one to use until we've parsed the header.
  1.3234 +bool
  1.3235 +nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
  1.3236 +                                 nsCookieAttributes &aCookieAttributes)
  1.3237 +{
  1.3238 +  static const char kPath[]    = "path";
  1.3239 +  static const char kDomain[]  = "domain";
  1.3240 +  static const char kExpires[] = "expires";
  1.3241 +  static const char kMaxage[]  = "max-age";
  1.3242 +  static const char kSecure[]  = "secure";
  1.3243 +  static const char kHttpOnly[]  = "httponly";
  1.3244 +
  1.3245 +  nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
  1.3246 +  nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
  1.3247 +  aCookieHeader.BeginReading(cookieStart);
  1.3248 +  aCookieHeader.EndReading(cookieEnd);
  1.3249 +
  1.3250 +  aCookieAttributes.isSecure = false;
  1.3251 +  aCookieAttributes.isHttpOnly = false;
  1.3252 +  
  1.3253 +  nsDependentCSubstring tokenString(cookieStart, cookieStart);
  1.3254 +  nsDependentCSubstring tokenValue (cookieStart, cookieStart);
  1.3255 +  bool newCookie, equalsFound;
  1.3256 +
  1.3257 +  // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
  1.3258 +  // if we find multiple cookies, return for processing
  1.3259 +  // note: if there's no '=', we assume token is <VALUE>. this is required by
  1.3260 +  //       some sites (see bug 169091).
  1.3261 +  // XXX fix the parser to parse according to <VALUE> grammar for this case
  1.3262 +  newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  1.3263 +  if (equalsFound) {
  1.3264 +    aCookieAttributes.name = tokenString;
  1.3265 +    aCookieAttributes.value = tokenValue;
  1.3266 +  } else {
  1.3267 +    aCookieAttributes.value = tokenString;
  1.3268 +  }
  1.3269 +
  1.3270 +  // extract remaining attributes
  1.3271 +  while (cookieStart != cookieEnd && !newCookie) {
  1.3272 +    newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  1.3273 +
  1.3274 +    if (!tokenValue.IsEmpty()) {
  1.3275 +      tokenValue.BeginReading(tempBegin);
  1.3276 +      tokenValue.EndReading(tempEnd);
  1.3277 +    }
  1.3278 +
  1.3279 +    // decide which attribute we have, and copy the string
  1.3280 +    if (tokenString.LowerCaseEqualsLiteral(kPath))
  1.3281 +      aCookieAttributes.path = tokenValue;
  1.3282 +
  1.3283 +    else if (tokenString.LowerCaseEqualsLiteral(kDomain))
  1.3284 +      aCookieAttributes.host = tokenValue;
  1.3285 +
  1.3286 +    else if (tokenString.LowerCaseEqualsLiteral(kExpires))
  1.3287 +      aCookieAttributes.expires = tokenValue;
  1.3288 +
  1.3289 +    else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
  1.3290 +      aCookieAttributes.maxage = tokenValue;
  1.3291 +
  1.3292 +    // ignore any tokenValue for isSecure; just set the boolean
  1.3293 +    else if (tokenString.LowerCaseEqualsLiteral(kSecure))
  1.3294 +      aCookieAttributes.isSecure = true;
  1.3295 +      
  1.3296 +    // ignore any tokenValue for isHttpOnly (see bug 178993);
  1.3297 +    // just set the boolean
  1.3298 +    else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
  1.3299 +      aCookieAttributes.isHttpOnly = true;
  1.3300 +  }
  1.3301 +
  1.3302 +  // rebind aCookieHeader, in case we need to process another cookie
  1.3303 +  aCookieHeader.Rebind(cookieStart, cookieEnd);
  1.3304 +  return newCookie;
  1.3305 +}
  1.3306 +
  1.3307 +/******************************************************************************
  1.3308 + * nsCookieService impl:
  1.3309 + * private domain & permission compliance enforcement functions
  1.3310 + ******************************************************************************/
  1.3311 +
  1.3312 +// Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
  1.3313 +// "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
  1.3314 +// dot may be present. If aHostURI is an IP address, an alias such as
  1.3315 +// 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
  1.3316 +// be the exact host, and aRequireHostMatch will be true to indicate that
  1.3317 +// substring matches should not be performed.
  1.3318 +nsresult
  1.3319 +nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
  1.3320 +                               nsCString &aBaseDomain,
  1.3321 +                               bool      &aRequireHostMatch)
  1.3322 +{
  1.3323 +  // get the base domain. this will fail if the host contains a leading dot,
  1.3324 +  // more than one trailing dot, or is otherwise malformed.
  1.3325 +  nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
  1.3326 +  aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  1.3327 +                      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
  1.3328 +  if (aRequireHostMatch) {
  1.3329 +    // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
  1.3330 +    // such as 'co.uk', or the empty string. use the host as a key in such
  1.3331 +    // cases.
  1.3332 +    rv = aHostURI->GetAsciiHost(aBaseDomain);
  1.3333 +  }
  1.3334 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3335 +
  1.3336 +  // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
  1.3337 +  if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
  1.3338 +    return NS_ERROR_INVALID_ARG;
  1.3339 +
  1.3340 +  // block any URIs without a host that aren't file:// URIs.
  1.3341 +  if (aBaseDomain.IsEmpty()) {
  1.3342 +    bool isFileURI = false;
  1.3343 +    aHostURI->SchemeIs("file", &isFileURI);
  1.3344 +    if (!isFileURI)
  1.3345 +      return NS_ERROR_INVALID_ARG;
  1.3346 +  }
  1.3347 +
  1.3348 +  return NS_OK;
  1.3349 +}
  1.3350 +
  1.3351 +// Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
  1.3352 +// "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
  1.3353 +// that aHost is already normalized, and it may contain a leading dot
  1.3354 +// (indicating that it represents a domain). A trailing dot may be present.
  1.3355 +// If aHost is an IP address, an alias such as 'localhost', an eTLD such as
  1.3356 +// 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
  1.3357 +// leading dot will be treated as an error.
  1.3358 +nsresult
  1.3359 +nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
  1.3360 +                                       nsCString        &aBaseDomain)
  1.3361 +{
  1.3362 +  // aHost must not be the string '.'.
  1.3363 +  if (aHost.Length() == 1 && aHost.Last() == '.')
  1.3364 +    return NS_ERROR_INVALID_ARG;
  1.3365 +
  1.3366 +  // aHost may contain a leading dot; if so, strip it now.
  1.3367 +  bool domain = !aHost.IsEmpty() && aHost.First() == '.';
  1.3368 +
  1.3369 +  // get the base domain. this will fail if the host contains a leading dot,
  1.3370 +  // more than one trailing dot, or is otherwise malformed.
  1.3371 +  nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
  1.3372 +  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  1.3373 +      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
  1.3374 +    // aHost is either an IP address, an alias such as 'localhost', an eTLD
  1.3375 +    // such as 'co.uk', or the empty string. use the host as a key in such
  1.3376 +    // cases; however, we reject any such hosts with a leading dot, since it
  1.3377 +    // doesn't make sense for them to be domain cookies.
  1.3378 +    if (domain)
  1.3379 +      return NS_ERROR_INVALID_ARG;
  1.3380 +
  1.3381 +    aBaseDomain = aHost;
  1.3382 +    return NS_OK;
  1.3383 +  }
  1.3384 +  return rv;
  1.3385 +}
  1.3386 +
  1.3387 +// Normalizes the given hostname, component by component. ASCII/ACE
  1.3388 +// components are lower-cased, and UTF-8 components are normalized per
  1.3389 +// RFC 3454 and converted to ACE.
  1.3390 +nsresult
  1.3391 +nsCookieService::NormalizeHost(nsCString &aHost)
  1.3392 +{
  1.3393 +  if (!IsASCII(aHost)) {
  1.3394 +    nsAutoCString host;
  1.3395 +    nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
  1.3396 +    if (NS_FAILED(rv))
  1.3397 +      return rv;
  1.3398 +
  1.3399 +    aHost = host;
  1.3400 +  }
  1.3401 +
  1.3402 +  ToLowerCase(aHost);
  1.3403 +  return NS_OK;
  1.3404 +}
  1.3405 +
  1.3406 +// returns true if 'a' is equal to or a subdomain of 'b',
  1.3407 +// assuming no leading dots are present.
  1.3408 +static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
  1.3409 +{
  1.3410 +  if (a == b)
  1.3411 +    return true;
  1.3412 +  if (a.Length() > b.Length())
  1.3413 +    return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
  1.3414 +  return false;
  1.3415 +}
  1.3416 +
  1.3417 +CookieStatus
  1.3418 +nsCookieService::CheckPrefs(nsIURI          *aHostURI,
  1.3419 +                            bool             aIsForeign,
  1.3420 +                            bool             aRequireHostMatch,
  1.3421 +                            const char      *aCookieHeader)
  1.3422 +{
  1.3423 +  nsresult rv;
  1.3424 +
  1.3425 +  // don't let ftp sites get/set cookies (could be a security issue)
  1.3426 +  bool ftp;
  1.3427 +  if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
  1.3428 +    COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
  1.3429 +    return STATUS_REJECTED_WITH_ERROR;
  1.3430 +  }
  1.3431 +
  1.3432 +  // check the permission list first; if we find an entry, it overrides
  1.3433 +  // default prefs. see bug 184059.
  1.3434 +  if (mPermissionService) {
  1.3435 +    nsCookieAccess access;
  1.3436 +    // Not passing an nsIChannel here is probably OK; our implementation
  1.3437 +    // doesn't do anything with it anyway.
  1.3438 +    rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
  1.3439 +
  1.3440 +    // if we found an entry, use it
  1.3441 +    if (NS_SUCCEEDED(rv)) {
  1.3442 +      switch (access) {
  1.3443 +      case nsICookiePermission::ACCESS_DENY:
  1.3444 +        COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  1.3445 +                          aCookieHeader, "cookies are blocked for this site");
  1.3446 +        return STATUS_REJECTED;
  1.3447 +
  1.3448 +      case nsICookiePermission::ACCESS_ALLOW:
  1.3449 +        return STATUS_ACCEPTED;
  1.3450 +
  1.3451 +      case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
  1.3452 +        if (aIsForeign) {
  1.3453 +          COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  1.3454 +                            aCookieHeader, "third party cookies are blocked "
  1.3455 +                            "for this site");
  1.3456 +          return STATUS_REJECTED;
  1.3457 +
  1.3458 +        }
  1.3459 +        return STATUS_ACCEPTED;
  1.3460 +
  1.3461 +      case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
  1.3462 +        if (!aIsForeign)
  1.3463 +          return STATUS_ACCEPTED;
  1.3464 +        uint32_t priorCookieCount = 0;
  1.3465 +        nsAutoCString hostFromURI;
  1.3466 +        aHostURI->GetHost(hostFromURI);
  1.3467 +        CountCookiesFromHost(hostFromURI, &priorCookieCount);
  1.3468 +        if (priorCookieCount == 0) {
  1.3469 +          COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  1.3470 +                            aCookieHeader, "third party cookies are blocked "
  1.3471 +                            "for this site");
  1.3472 +          return STATUS_REJECTED;
  1.3473 +        }
  1.3474 +        return STATUS_ACCEPTED;
  1.3475 +      }
  1.3476 +    }
  1.3477 +  }
  1.3478 +
  1.3479 +  // check default prefs
  1.3480 +  if (mCookieBehavior == BEHAVIOR_REJECT) {
  1.3481 +    COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
  1.3482 +    return STATUS_REJECTED;
  1.3483 +  }
  1.3484 +
  1.3485 +  // check if cookie is foreign
  1.3486 +  if (aIsForeign) {
  1.3487 +    if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
  1.3488 +      return STATUS_ACCEPT_SESSION;
  1.3489 +
  1.3490 +    if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
  1.3491 +      COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  1.3492 +      return STATUS_REJECTED;
  1.3493 +    }
  1.3494 +
  1.3495 +    if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) {
  1.3496 +      uint32_t priorCookieCount = 0;
  1.3497 +      nsAutoCString hostFromURI;
  1.3498 +      aHostURI->GetHost(hostFromURI);
  1.3499 +      CountCookiesFromHost(hostFromURI, &priorCookieCount);
  1.3500 +      if (priorCookieCount == 0) {
  1.3501 +        COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  1.3502 +        return STATUS_REJECTED;
  1.3503 +      }
  1.3504 +      if (mThirdPartySession)
  1.3505 +        return STATUS_ACCEPT_SESSION;
  1.3506 +    }
  1.3507 +  }
  1.3508 +
  1.3509 +  // if nothing has complained, accept cookie
  1.3510 +  return STATUS_ACCEPTED;
  1.3511 +}
  1.3512 +
  1.3513 +// processes domain attribute, and returns true if host has permission to set for this domain.
  1.3514 +bool
  1.3515 +nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
  1.3516 +                             nsIURI             *aHostURI,
  1.3517 +                             const nsCString    &aBaseDomain,
  1.3518 +                             bool                aRequireHostMatch)
  1.3519 +{
  1.3520 +  // get host from aHostURI
  1.3521 +  nsAutoCString hostFromURI;
  1.3522 +  aHostURI->GetAsciiHost(hostFromURI);
  1.3523 +
  1.3524 +  // if a domain is given, check the host has permission
  1.3525 +  if (!aCookieAttributes.host.IsEmpty()) {
  1.3526 +    // Tolerate leading '.' characters, but not if it's otherwise an empty host.
  1.3527 +    if (aCookieAttributes.host.Length() > 1 &&
  1.3528 +        aCookieAttributes.host.First() == '.') {
  1.3529 +      aCookieAttributes.host.Cut(0, 1);
  1.3530 +    }
  1.3531 +
  1.3532 +    // switch to lowercase now, to avoid case-insensitive compares everywhere
  1.3533 +    ToLowerCase(aCookieAttributes.host);
  1.3534 +
  1.3535 +    // check whether the host is either an IP address, an alias such as
  1.3536 +    // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
  1.3537 +    // cases, require an exact string match for the domain, and leave the cookie
  1.3538 +    // as a non-domain one. bug 105917 originally noted the requirement to deal
  1.3539 +    // with IP addresses.
  1.3540 +    if (aRequireHostMatch)
  1.3541 +      return hostFromURI.Equals(aCookieAttributes.host);
  1.3542 +
  1.3543 +    // ensure the proposed domain is derived from the base domain; and also
  1.3544 +    // that the host domain is derived from the proposed domain (per RFC2109).
  1.3545 +    if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
  1.3546 +        IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
  1.3547 +      // prepend a dot to indicate a domain cookie
  1.3548 +      aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
  1.3549 +      return true;
  1.3550 +    }
  1.3551 +
  1.3552 +    /*
  1.3553 +     * note: RFC2109 section 4.3.2 requires that we check the following:
  1.3554 +     * that the portion of host not in domain does not contain a dot.
  1.3555 +     * this prevents hosts of the form x.y.co.nz from setting cookies in the
  1.3556 +     * entire .co.nz domain. however, it's only a only a partial solution and
  1.3557 +     * it breaks sites (IE doesn't enforce it), so we don't perform this check.
  1.3558 +     */
  1.3559 +    return false;
  1.3560 +  }
  1.3561 +
  1.3562 +  // no domain specified, use hostFromURI
  1.3563 +  aCookieAttributes.host = hostFromURI;
  1.3564 +  return true;
  1.3565 +}
  1.3566 +
  1.3567 +bool
  1.3568 +nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
  1.3569 +                           nsIURI             *aHostURI)
  1.3570 +{
  1.3571 +  // if a path is given, check the host has permission
  1.3572 +  if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
  1.3573 +    // strip down everything after the last slash to get the path,
  1.3574 +    // ignoring slashes in the query string part.
  1.3575 +    // if we can QI to nsIURL, that'll take care of the query string portion.
  1.3576 +    // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
  1.3577 +    nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
  1.3578 +    if (hostURL) {
  1.3579 +      hostURL->GetDirectory(aCookieAttributes.path);
  1.3580 +    } else {
  1.3581 +      aHostURI->GetPath(aCookieAttributes.path);
  1.3582 +      int32_t slash = aCookieAttributes.path.RFindChar('/');
  1.3583 +      if (slash != kNotFound) {
  1.3584 +        aCookieAttributes.path.Truncate(slash + 1);
  1.3585 +      }
  1.3586 +    }
  1.3587 +
  1.3588 +#if 0
  1.3589 +  } else {
  1.3590 +    /**
  1.3591 +     * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
  1.3592 +     * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
  1.3593 +     * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
  1.3594 +     * been disabled, unless we can evangelize these sites.
  1.3595 +     */
  1.3596 +    // get path from aHostURI
  1.3597 +    nsAutoCString pathFromURI;
  1.3598 +    if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
  1.3599 +        !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
  1.3600 +      return false;
  1.3601 +    }
  1.3602 +#endif
  1.3603 +  }
  1.3604 +
  1.3605 +  if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
  1.3606 +      aCookieAttributes.path.FindChar('\t') != kNotFound )
  1.3607 +    return false;
  1.3608 +
  1.3609 +  return true;
  1.3610 +}
  1.3611 +
  1.3612 +bool
  1.3613 +nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
  1.3614 +                           int64_t             aServerTime,
  1.3615 +                           int64_t             aCurrentTime)
  1.3616 +{
  1.3617 +  /* Determine when the cookie should expire. This is done by taking the difference between 
  1.3618 +   * the server time and the time the server wants the cookie to expire, and adding that 
  1.3619 +   * difference to the client time. This localizes the client time regardless of whether or
  1.3620 +   * not the TZ environment variable was set on the client.
  1.3621 +   *
  1.3622 +   * Note: We need to consider accounting for network lag here, per RFC.
  1.3623 +   */
  1.3624 +  int64_t delta;
  1.3625 +
  1.3626 +  // check for max-age attribute first; this overrides expires attribute
  1.3627 +  if (!aCookieAttributes.maxage.IsEmpty()) {
  1.3628 +    // obtain numeric value of maxageAttribute
  1.3629 +    int64_t maxage;
  1.3630 +    int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
  1.3631 +
  1.3632 +    // default to session cookie if the conversion failed
  1.3633 +    if (numInts != 1) {
  1.3634 +      return true;
  1.3635 +    }
  1.3636 +
  1.3637 +    delta = maxage;
  1.3638 +
  1.3639 +  // check for expires attribute
  1.3640 +  } else if (!aCookieAttributes.expires.IsEmpty()) {
  1.3641 +    PRTime expires;
  1.3642 +
  1.3643 +    // parse expiry time
  1.3644 +    if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
  1.3645 +      return true;
  1.3646 +    }
  1.3647 +
  1.3648 +    delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
  1.3649 +
  1.3650 +  // default to session cookie if no attributes found
  1.3651 +  } else {
  1.3652 +    return true;
  1.3653 +  }
  1.3654 +
  1.3655 +  // if this addition overflows, expiryTime will be less than currentTime
  1.3656 +  // and the cookie will be expired - that's okay.
  1.3657 +  aCookieAttributes.expiryTime = aCurrentTime + delta;
  1.3658 +
  1.3659 +  return false;
  1.3660 +}
  1.3661 +
  1.3662 +/******************************************************************************
  1.3663 + * nsCookieService impl:
  1.3664 + * private cookielist management functions
  1.3665 + ******************************************************************************/
  1.3666 +
  1.3667 +void
  1.3668 +nsCookieService::RemoveAllFromMemory()
  1.3669 +{
  1.3670 +  // clearing the hashtable will call each nsCookieEntry's dtor,
  1.3671 +  // which releases all their respective children.
  1.3672 +  mDBState->hostTable.Clear();
  1.3673 +  mDBState->cookieCount = 0;
  1.3674 +  mDBState->cookieOldestTime = INT64_MAX;
  1.3675 +}
  1.3676 +
  1.3677 +// stores temporary data for enumerating over the hash entries,
  1.3678 +// since enumeration is done using callback functions
  1.3679 +struct nsPurgeData
  1.3680 +{
  1.3681 +  typedef nsTArray<nsListIter> ArrayType;
  1.3682 +
  1.3683 +  nsPurgeData(int64_t aCurrentTime,
  1.3684 +              int64_t aPurgeTime,
  1.3685 +              ArrayType &aPurgeList,
  1.3686 +              nsIMutableArray *aRemovedList,
  1.3687 +              mozIStorageBindingParamsArray *aParamsArray)
  1.3688 +   : currentTime(aCurrentTime)
  1.3689 +   , purgeTime(aPurgeTime)
  1.3690 +   , oldestTime(INT64_MAX)
  1.3691 +   , purgeList(aPurgeList)
  1.3692 +   , removedList(aRemovedList)
  1.3693 +   , paramsArray(aParamsArray)
  1.3694 +  {
  1.3695 +  }
  1.3696 +
  1.3697 +  // the current time, in seconds
  1.3698 +  int64_t currentTime;
  1.3699 +
  1.3700 +  // lastAccessed time older than which cookies are eligible for purge
  1.3701 +  int64_t purgeTime;
  1.3702 +
  1.3703 +  // lastAccessed time of the oldest cookie found during purge, to update our indicator
  1.3704 +  int64_t oldestTime;
  1.3705 +
  1.3706 +  // list of cookies over the age limit, for purging
  1.3707 +  ArrayType &purgeList;
  1.3708 +
  1.3709 +  // list of all cookies we've removed, for notification
  1.3710 +  nsIMutableArray *removedList;
  1.3711 +
  1.3712 +  // The array of parameters to be bound to the statement for deletion later.
  1.3713 +  mozIStorageBindingParamsArray *paramsArray;
  1.3714 +};
  1.3715 +
  1.3716 +// comparator class for lastaccessed times of cookies.
  1.3717 +class CompareCookiesByAge {
  1.3718 +public:
  1.3719 +  bool Equals(const nsListIter &a, const nsListIter &b) const
  1.3720 +  {
  1.3721 +    return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
  1.3722 +           a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
  1.3723 +  }
  1.3724 +
  1.3725 +  bool LessThan(const nsListIter &a, const nsListIter &b) const
  1.3726 +  {
  1.3727 +    // compare by lastAccessed time, and tiebreak by creationTime.
  1.3728 +    int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
  1.3729 +    if (result != 0)
  1.3730 +      return result < 0;
  1.3731 +
  1.3732 +    return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
  1.3733 +  }
  1.3734 +};
  1.3735 +
  1.3736 +// comparator class for sorting cookies by entry and index.
  1.3737 +class CompareCookiesByIndex {
  1.3738 +public:
  1.3739 +  bool Equals(const nsListIter &a, const nsListIter &b) const
  1.3740 +  {
  1.3741 +    NS_ASSERTION(a.entry != b.entry || a.index != b.index,
  1.3742 +      "cookie indexes should never be equal");
  1.3743 +    return false;
  1.3744 +  }
  1.3745 +
  1.3746 +  bool LessThan(const nsListIter &a, const nsListIter &b) const
  1.3747 +  {
  1.3748 +    // compare by entryclass pointer, then by index.
  1.3749 +    if (a.entry != b.entry)
  1.3750 +      return a.entry < b.entry;
  1.3751 +
  1.3752 +    return a.index < b.index;
  1.3753 +  }
  1.3754 +};
  1.3755 +
  1.3756 +PLDHashOperator
  1.3757 +purgeCookiesCallback(nsCookieEntry *aEntry,
  1.3758 +                     void          *aArg)
  1.3759 +{
  1.3760 +  nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
  1.3761 +
  1.3762 +  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1.3763 +  mozIStorageBindingParamsArray *array = data.paramsArray;
  1.3764 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
  1.3765 +    nsListIter iter(aEntry, i);
  1.3766 +    nsCookie *cookie = cookies[i];
  1.3767 +
  1.3768 +    // check if the cookie has expired
  1.3769 +    if (cookie->Expiry() <= data.currentTime) {
  1.3770 +      data.removedList->AppendElement(cookie, false);
  1.3771 +      COOKIE_LOGEVICTED(cookie, "Cookie expired");
  1.3772 +
  1.3773 +      // remove from list; do not increment our iterator
  1.3774 +      gCookieService->RemoveCookieFromList(iter, array);
  1.3775 +
  1.3776 +    } else {
  1.3777 +      // check if the cookie is over the age limit
  1.3778 +      if (cookie->LastAccessed() <= data.purgeTime) {
  1.3779 +        data.purgeList.AppendElement(iter);
  1.3780 +
  1.3781 +      } else if (cookie->LastAccessed() < data.oldestTime) {
  1.3782 +        // reset our indicator
  1.3783 +        data.oldestTime = cookie->LastAccessed();
  1.3784 +      }
  1.3785 +
  1.3786 +      ++i;
  1.3787 +    }
  1.3788 +  }
  1.3789 +  return PL_DHASH_NEXT;
  1.3790 +}
  1.3791 +
  1.3792 +// purges expired and old cookies in a batch operation.
  1.3793 +already_AddRefed<nsIArray>
  1.3794 +nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
  1.3795 +{
  1.3796 +  NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
  1.3797 +  EnsureReadComplete();
  1.3798 +
  1.3799 +#ifdef PR_LOGGING
  1.3800 +  uint32_t initialCookieCount = mDBState->cookieCount;
  1.3801 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.3802 +    ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
  1.3803 +     mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
  1.3804 +#endif
  1.3805 +
  1.3806 +  nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
  1.3807 +
  1.3808 +  nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
  1.3809 +
  1.3810 +  // Create a params array to batch the removals. This is OK here because
  1.3811 +  // all the removals are in order, and there are no interleaved additions.
  1.3812 +  mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  1.3813 +  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1.3814 +  if (mDBState->dbConn) {
  1.3815 +    stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.3816 +  }
  1.3817 +
  1.3818 +  nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
  1.3819 +    aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
  1.3820 +  mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
  1.3821 +
  1.3822 +#ifdef PR_LOGGING
  1.3823 +  uint32_t postExpiryCookieCount = mDBState->cookieCount;
  1.3824 +#endif
  1.3825 +
  1.3826 +  // now we have a list of iterators for cookies over the age limit.
  1.3827 +  // sort them by age, and then we'll see how many to remove...
  1.3828 +  purgeList.Sort(CompareCookiesByAge());
  1.3829 +
  1.3830 +  // only remove old cookies until we reach the max cookie limit, no more.
  1.3831 +  uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
  1.3832 +    mDBState->cookieCount - mMaxNumberOfCookies : 0;
  1.3833 +  if (purgeList.Length() > excess) {
  1.3834 +    // We're not purging everything in the list, so update our indicator.
  1.3835 +    data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
  1.3836 +
  1.3837 +    purgeList.SetLength(excess);
  1.3838 +  }
  1.3839 +
  1.3840 +  // sort the list again, this time grouping cookies with a common entryclass
  1.3841 +  // together, and with ascending index. this allows us to iterate backwards
  1.3842 +  // over the list removing cookies, without having to adjust indexes as we go.
  1.3843 +  purgeList.Sort(CompareCookiesByIndex());
  1.3844 +  for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
  1.3845 +    nsCookie *cookie = purgeList[i].Cookie();
  1.3846 +    removedList->AppendElement(cookie, false);
  1.3847 +    COOKIE_LOGEVICTED(cookie, "Cookie too old");
  1.3848 +
  1.3849 +    RemoveCookieFromList(purgeList[i], paramsArray);
  1.3850 +  }
  1.3851 +
  1.3852 +  // Update the database if we have entries to purge.
  1.3853 +  if (paramsArray) {
  1.3854 +    uint32_t length;
  1.3855 +    paramsArray->GetLength(&length);
  1.3856 +    if (length) {
  1.3857 +      DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1.3858 +      NS_ASSERT_SUCCESS(rv);
  1.3859 +      nsCOMPtr<mozIStoragePendingStatement> handle;
  1.3860 +      rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  1.3861 +      NS_ASSERT_SUCCESS(rv);
  1.3862 +    }
  1.3863 +  }
  1.3864 +
  1.3865 +  // reset the oldest time indicator
  1.3866 +  mDBState->cookieOldestTime = data.oldestTime;
  1.3867 +
  1.3868 +  COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1.3869 +    ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
  1.3870 +     initialCookieCount - postExpiryCookieCount,
  1.3871 +     postExpiryCookieCount - mDBState->cookieCount,
  1.3872 +     mDBState->cookieCount,
  1.3873 +     aCurrentTimeInUsec - mDBState->cookieOldestTime));
  1.3874 +
  1.3875 +  return removedList.forget();
  1.3876 +}
  1.3877 +
  1.3878 +// find whether a given cookie has been previously set. this is provided by the
  1.3879 +// nsICookieManager2 interface.
  1.3880 +NS_IMETHODIMP
  1.3881 +nsCookieService::CookieExists(nsICookie2 *aCookie,
  1.3882 +                              bool       *aFoundCookie)
  1.3883 +{
  1.3884 +  NS_ENSURE_ARG_POINTER(aCookie);
  1.3885 +
  1.3886 +  if (!mDBState) {
  1.3887 +    NS_WARNING("No DBState! Profile already closed?");
  1.3888 +    return NS_ERROR_NOT_AVAILABLE;
  1.3889 +  }
  1.3890 +
  1.3891 +  nsAutoCString host, name, path;
  1.3892 +  nsresult rv = aCookie->GetHost(host);
  1.3893 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3894 +  rv = aCookie->GetName(name);
  1.3895 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3896 +  rv = aCookie->GetPath(path);
  1.3897 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3898 +
  1.3899 +  nsAutoCString baseDomain;
  1.3900 +  rv = GetBaseDomainFromHost(host, baseDomain);
  1.3901 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3902 +
  1.3903 +  nsListIter iter;
  1.3904 +  *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), host, name, path, iter);
  1.3905 +  return NS_OK;
  1.3906 +}
  1.3907 +
  1.3908 +// For a given base domain, find either an expired cookie or the oldest cookie
  1.3909 +// by lastAccessed time.
  1.3910 +void
  1.3911 +nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
  1.3912 +                                 int64_t aCurrentTime,
  1.3913 +                                 nsListIter &aIter)
  1.3914 +{
  1.3915 +  aIter.entry = nullptr;
  1.3916 +
  1.3917 +  int64_t oldestTime = 0;
  1.3918 +  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1.3919 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.3920 +    nsCookie *cookie = cookies[i];
  1.3921 +
  1.3922 +    // If we found an expired cookie, we're done.
  1.3923 +    if (cookie->Expiry() <= aCurrentTime) {
  1.3924 +      aIter.entry = aEntry;
  1.3925 +      aIter.index = i;
  1.3926 +      return;
  1.3927 +    }
  1.3928 +
  1.3929 +    // Check if we've found the oldest cookie so far.
  1.3930 +    if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
  1.3931 +      oldestTime = cookie->LastAccessed();
  1.3932 +      aIter.entry = aEntry;
  1.3933 +      aIter.index = i;
  1.3934 +    }
  1.3935 +  }
  1.3936 +}
  1.3937 +
  1.3938 +// count the number of cookies stored by a particular host. this is provided by the
  1.3939 +// nsICookieManager2 interface.
  1.3940 +NS_IMETHODIMP
  1.3941 +nsCookieService::CountCookiesFromHost(const nsACString &aHost,
  1.3942 +                                      uint32_t         *aCountFromHost)
  1.3943 +{
  1.3944 +  if (!mDBState) {
  1.3945 +    NS_WARNING("No DBState! Profile already closed?");
  1.3946 +    return NS_ERROR_NOT_AVAILABLE;
  1.3947 +  }
  1.3948 +
  1.3949 +  // first, normalize the hostname, and fail if it contains illegal characters.
  1.3950 +  nsAutoCString host(aHost);
  1.3951 +  nsresult rv = NormalizeHost(host);
  1.3952 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3953 +
  1.3954 +  nsAutoCString baseDomain;
  1.3955 +  rv = GetBaseDomainFromHost(host, baseDomain);
  1.3956 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3957 +
  1.3958 +  nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  1.3959 +  EnsureReadDomain(key);
  1.3960 +
  1.3961 +  // Return a count of all cookies, including expired.
  1.3962 +  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  1.3963 +  *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
  1.3964 +  return NS_OK;
  1.3965 +}
  1.3966 +
  1.3967 +// get an enumerator of cookies stored by a particular host. this is provided by the
  1.3968 +// nsICookieManager2 interface.
  1.3969 +NS_IMETHODIMP
  1.3970 +nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
  1.3971 +                                    nsISimpleEnumerator **aEnumerator)
  1.3972 +{
  1.3973 +  if (!mDBState) {
  1.3974 +    NS_WARNING("No DBState! Profile already closed?");
  1.3975 +    return NS_ERROR_NOT_AVAILABLE;
  1.3976 +  }
  1.3977 +
  1.3978 +  // first, normalize the hostname, and fail if it contains illegal characters.
  1.3979 +  nsAutoCString host(aHost);
  1.3980 +  nsresult rv = NormalizeHost(host);
  1.3981 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3982 +
  1.3983 +  nsAutoCString baseDomain;
  1.3984 +  rv = GetBaseDomainFromHost(host, baseDomain);
  1.3985 +  NS_ENSURE_SUCCESS(rv, rv);
  1.3986 +
  1.3987 +  nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  1.3988 +  EnsureReadDomain(key);
  1.3989 +
  1.3990 +  nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  1.3991 +  if (!entry)
  1.3992 +    return NS_NewEmptyEnumerator(aEnumerator);
  1.3993 +
  1.3994 +  nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
  1.3995 +  const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  1.3996 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.3997 +    cookieList.AppendObject(cookies[i]);
  1.3998 +  }
  1.3999 +
  1.4000 +  return NS_NewArrayEnumerator(aEnumerator, cookieList);
  1.4001 +}
  1.4002 +
  1.4003 +namespace {
  1.4004 +
  1.4005 +/**
  1.4006 + * This structure is used as a in/out parameter when enumerating the cookies
  1.4007 + * for an app.
  1.4008 + * It will contain the app id and onlyBrowserElement flag information as input
  1.4009 + * and will contain the array of matching cookies as output.
  1.4010 + */
  1.4011 +struct GetCookiesForAppStruct {
  1.4012 +  uint32_t              appId;
  1.4013 +  bool                  onlyBrowserElement;
  1.4014 +  nsCOMArray<nsICookie> cookies;
  1.4015 +
  1.4016 +  GetCookiesForAppStruct() MOZ_DELETE;
  1.4017 +  GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
  1.4018 +    : appId(aAppId)
  1.4019 +    , onlyBrowserElement(aOnlyBrowserElement)
  1.4020 +  {}
  1.4021 +};
  1.4022 +
  1.4023 +} // anonymous namespace
  1.4024 +
  1.4025 +/* static */ PLDHashOperator
  1.4026 +nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
  1.4027 +{
  1.4028 +  GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
  1.4029 +
  1.4030 +  if (entry->mAppId != data->appId ||
  1.4031 +      (data->onlyBrowserElement && !entry->mInBrowserElement)) {
  1.4032 +    return PL_DHASH_NEXT;
  1.4033 +  }
  1.4034 +
  1.4035 +  const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
  1.4036 +
  1.4037 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.4038 +    data->cookies.AppendObject(cookies[i]);
  1.4039 +  }
  1.4040 +
  1.4041 +  return PL_DHASH_NEXT;
  1.4042 +}
  1.4043 +
  1.4044 +NS_IMETHODIMP
  1.4045 +nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
  1.4046 +                                  nsISimpleEnumerator** aEnumerator)
  1.4047 +{
  1.4048 +  if (!mDBState) {
  1.4049 +    NS_WARNING("No DBState! Profile already closed?");
  1.4050 +    return NS_ERROR_NOT_AVAILABLE;
  1.4051 +  }
  1.4052 +
  1.4053 +  NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
  1.4054 +
  1.4055 +  GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
  1.4056 +  mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
  1.4057 +
  1.4058 +  return NS_NewArrayEnumerator(aEnumerator, data.cookies);
  1.4059 +}
  1.4060 +
  1.4061 +NS_IMETHODIMP
  1.4062 +nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
  1.4063 +{
  1.4064 +  nsCOMPtr<nsISimpleEnumerator> enumerator;
  1.4065 +  nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
  1.4066 +                                 getter_AddRefs(enumerator));
  1.4067 +
  1.4068 +  NS_ENSURE_SUCCESS(rv, rv);
  1.4069 +
  1.4070 +  bool hasMore;
  1.4071 +  while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
  1.4072 +    nsCOMPtr<nsISupports> supports;
  1.4073 +    nsCOMPtr<nsICookie> cookie;
  1.4074 +    rv = enumerator->GetNext(getter_AddRefs(supports));
  1.4075 +    NS_ENSURE_SUCCESS(rv, rv);
  1.4076 +
  1.4077 +    cookie = do_QueryInterface(supports);
  1.4078 +
  1.4079 +    nsAutoCString host;
  1.4080 +    cookie->GetHost(host);
  1.4081 +
  1.4082 +    nsAutoCString name;
  1.4083 +    cookie->GetName(name);
  1.4084 +
  1.4085 +    nsAutoCString path;
  1.4086 +    cookie->GetPath(path);
  1.4087 +
  1.4088 +    // nsICookie do not carry the appId/inBrowserElement information.
  1.4089 +    // That means we have to guess. This is easy for appId but not for
  1.4090 +    // inBrowserElement flag.
  1.4091 +    // A simple solution is to always ask to remove the cookie with
  1.4092 +    // inBrowserElement = true and only ask for the other one to be removed if
  1.4093 +    // we happen to be in the case of !aOnlyBrowserElement.
  1.4094 +    // Anyway, with this solution, we will likely be looking for unexistant
  1.4095 +    // cookies.
  1.4096 +    //
  1.4097 +    // NOTE: we could make this better by getting nsCookieEntry objects instead
  1.4098 +    // of plain nsICookie.
  1.4099 +    Remove(host, aAppId, true, name, path, false);
  1.4100 +    if (!aOnlyBrowserElement) {
  1.4101 +      Remove(host, aAppId, false, name, path, false);
  1.4102 +    }
  1.4103 +  }
  1.4104 +
  1.4105 +  return NS_OK;
  1.4106 +}
  1.4107 +
  1.4108 +// find an exact cookie specified by host, name, and path that hasn't expired.
  1.4109 +bool
  1.4110 +nsCookieService::FindCookie(const nsCookieKey    &aKey,
  1.4111 +                            const nsAFlatCString &aHost,
  1.4112 +                            const nsAFlatCString &aName,
  1.4113 +                            const nsAFlatCString &aPath,
  1.4114 +                            nsListIter           &aIter)
  1.4115 +{
  1.4116 +  EnsureReadDomain(aKey);
  1.4117 +
  1.4118 +  nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  1.4119 +  if (!entry)
  1.4120 +    return false;
  1.4121 +
  1.4122 +  const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  1.4123 +  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1.4124 +    nsCookie *cookie = cookies[i];
  1.4125 +
  1.4126 +    if (aHost.Equals(cookie->Host()) &&
  1.4127 +        aPath.Equals(cookie->Path()) &&
  1.4128 +        aName.Equals(cookie->Name())) {
  1.4129 +      aIter = nsListIter(entry, i);
  1.4130 +      return true;
  1.4131 +    }
  1.4132 +  }
  1.4133 +
  1.4134 +  return false;
  1.4135 +}
  1.4136 +
  1.4137 +// remove a cookie from the hashtable, and update the iterator state.
  1.4138 +void
  1.4139 +nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
  1.4140 +                                      mozIStorageBindingParamsArray *aParamsArray)
  1.4141 +{
  1.4142 +  // if it's a non-session cookie, remove it from the db
  1.4143 +  if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
  1.4144 +    // Use the asynchronous binding methods to ensure that we do not acquire
  1.4145 +    // the database lock.
  1.4146 +    mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  1.4147 +    nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  1.4148 +    if (!paramsArray) {
  1.4149 +      stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.4150 +    }
  1.4151 +
  1.4152 +    nsCOMPtr<mozIStorageBindingParams> params;
  1.4153 +    paramsArray->NewBindingParams(getter_AddRefs(params));
  1.4154 +
  1.4155 +    DebugOnly<nsresult> rv =
  1.4156 +      params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  1.4157 +                                   aIter.Cookie()->Name());
  1.4158 +    NS_ASSERT_SUCCESS(rv);
  1.4159 +
  1.4160 +    rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  1.4161 +                                      aIter.Cookie()->Host());
  1.4162 +    NS_ASSERT_SUCCESS(rv);
  1.4163 +
  1.4164 +    rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  1.4165 +                                      aIter.Cookie()->Path());
  1.4166 +    NS_ASSERT_SUCCESS(rv);
  1.4167 +
  1.4168 +    rv = paramsArray->AddParams(params);
  1.4169 +    NS_ASSERT_SUCCESS(rv);
  1.4170 +
  1.4171 +    // If we weren't given a params array, we'll need to remove it ourselves.
  1.4172 +    if (!aParamsArray) {
  1.4173 +      rv = stmt->BindParameters(paramsArray);
  1.4174 +      NS_ASSERT_SUCCESS(rv);
  1.4175 +      nsCOMPtr<mozIStoragePendingStatement> handle;
  1.4176 +      rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  1.4177 +      NS_ASSERT_SUCCESS(rv);
  1.4178 +    }
  1.4179 +  }
  1.4180 +
  1.4181 +  if (aIter.entry->GetCookies().Length() == 1) {
  1.4182 +    // we're removing the last element in the array - so just remove the entry
  1.4183 +    // from the hash. note that the entryclass' dtor will take care of
  1.4184 +    // releasing this last element for us!
  1.4185 +    mDBState->hostTable.RawRemoveEntry(aIter.entry);
  1.4186 +
  1.4187 +  } else {
  1.4188 +    // just remove the element from the list
  1.4189 +    aIter.entry->GetCookies().RemoveElementAt(aIter.index);
  1.4190 +  }
  1.4191 +
  1.4192 +  --mDBState->cookieCount;
  1.4193 +}
  1.4194 +
  1.4195 +void
  1.4196 +bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
  1.4197 +                     const nsCookieKey &aKey,
  1.4198 +                     const nsCookie *aCookie)
  1.4199 +{
  1.4200 +  NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
  1.4201 +  NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
  1.4202 +
  1.4203 +  // Use the asynchronous binding methods to ensure that we do not acquire the
  1.4204 +  // database lock.
  1.4205 +  nsCOMPtr<mozIStorageBindingParams> params;
  1.4206 +  DebugOnly<nsresult> rv =
  1.4207 +    aParamsArray->NewBindingParams(getter_AddRefs(params));
  1.4208 +  NS_ASSERT_SUCCESS(rv);
  1.4209 +
  1.4210 +  // Bind our values to params
  1.4211 +  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
  1.4212 +                                    aKey.mBaseDomain);
  1.4213 +  NS_ASSERT_SUCCESS(rv);
  1.4214 +
  1.4215 +  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
  1.4216 +                               aKey.mAppId);
  1.4217 +  NS_ASSERT_SUCCESS(rv);
  1.4218 +
  1.4219 +  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
  1.4220 +                               aKey.mInBrowserElement ? 1 : 0);
  1.4221 +  NS_ASSERT_SUCCESS(rv);
  1.4222 +
  1.4223 +  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  1.4224 +                                    aCookie->Name());
  1.4225 +  NS_ASSERT_SUCCESS(rv);
  1.4226 +
  1.4227 +  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
  1.4228 +                                    aCookie->Value());
  1.4229 +  NS_ASSERT_SUCCESS(rv);
  1.4230 +
  1.4231 +  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  1.4232 +                                    aCookie->Host());
  1.4233 +  NS_ASSERT_SUCCESS(rv);
  1.4234 +
  1.4235 +  rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  1.4236 +                                    aCookie->Path());
  1.4237 +  NS_ASSERT_SUCCESS(rv);
  1.4238 +
  1.4239 +  rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
  1.4240 +                               aCookie->Expiry());
  1.4241 +  NS_ASSERT_SUCCESS(rv);
  1.4242 +
  1.4243 +  rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  1.4244 +                               aCookie->LastAccessed());
  1.4245 +  NS_ASSERT_SUCCESS(rv);
  1.4246 +
  1.4247 +  rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
  1.4248 +                               aCookie->CreationTime());
  1.4249 +  NS_ASSERT_SUCCESS(rv);
  1.4250 +
  1.4251 +  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
  1.4252 +                               aCookie->IsSecure());
  1.4253 +  NS_ASSERT_SUCCESS(rv);
  1.4254 +
  1.4255 +  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
  1.4256 +                               aCookie->IsHttpOnly());
  1.4257 +  NS_ASSERT_SUCCESS(rv);
  1.4258 +
  1.4259 +  // Bind the params to the array.
  1.4260 +  rv = aParamsArray->AddParams(params);
  1.4261 +  NS_ASSERT_SUCCESS(rv);
  1.4262 +}
  1.4263 +
  1.4264 +void
  1.4265 +nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
  1.4266 +                                 nsCookie                      *aCookie,
  1.4267 +                                 DBState                       *aDBState,
  1.4268 +                                 mozIStorageBindingParamsArray *aParamsArray,
  1.4269 +                                 bool                           aWriteToDB)
  1.4270 +{
  1.4271 +  NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
  1.4272 +               "Not writing to the DB but have a params array?");
  1.4273 +  NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
  1.4274 +               "Do not have a DB connection but have a params array?");
  1.4275 +
  1.4276 +  nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
  1.4277 +  NS_ASSERTION(entry, "can't insert element into a null entry!");
  1.4278 +
  1.4279 +  entry->GetCookies().AppendElement(aCookie);
  1.4280 +  ++aDBState->cookieCount;
  1.4281 +
  1.4282 +  // keep track of the oldest cookie, for when it comes time to purge
  1.4283 +  if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
  1.4284 +    aDBState->cookieOldestTime = aCookie->LastAccessed();
  1.4285 +
  1.4286 +  // if it's a non-session cookie and hasn't just been read from the db, write it out.
  1.4287 +  if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
  1.4288 +    mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
  1.4289 +    nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  1.4290 +    if (!paramsArray) {
  1.4291 +      stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1.4292 +    }
  1.4293 +    bindCookieParameters(paramsArray, aKey, aCookie);
  1.4294 +
  1.4295 +    // If we were supplied an array to store parameters, we shouldn't call
  1.4296 +    // executeAsync - someone up the stack will do this for us.
  1.4297 +    if (!aParamsArray) {
  1.4298 +      DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1.4299 +      NS_ASSERT_SUCCESS(rv);
  1.4300 +      nsCOMPtr<mozIStoragePendingStatement> handle;
  1.4301 +      rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
  1.4302 +      NS_ASSERT_SUCCESS(rv);
  1.4303 +    }
  1.4304 +  }
  1.4305 +}
  1.4306 +
  1.4307 +void
  1.4308 +nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
  1.4309 +                                    int64_t                        aLastAccessed,
  1.4310 +                                    mozIStorageBindingParamsArray *aParamsArray)
  1.4311 +{
  1.4312 +  NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
  1.4313 +
  1.4314 +  // udpate the lastAccessed timestamp
  1.4315 +  aCookie->SetLastAccessed(aLastAccessed);
  1.4316 +
  1.4317 +  // if it's a non-session cookie, update it in the db too
  1.4318 +  if (!aCookie->IsSession() && aParamsArray) {
  1.4319 +    // Create our params holder.
  1.4320 +    nsCOMPtr<mozIStorageBindingParams> params;
  1.4321 +    aParamsArray->NewBindingParams(getter_AddRefs(params));
  1.4322 +
  1.4323 +    // Bind our parameters.
  1.4324 +    DebugOnly<nsresult> rv =
  1.4325 +      params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  1.4326 +                              aLastAccessed);
  1.4327 +    NS_ASSERT_SUCCESS(rv);
  1.4328 +
  1.4329 +    rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  1.4330 +                                      aCookie->Name());
  1.4331 +    NS_ASSERT_SUCCESS(rv);
  1.4332 +
  1.4333 +    rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  1.4334 +                                      aCookie->Host());
  1.4335 +    NS_ASSERT_SUCCESS(rv);
  1.4336 +
  1.4337 +    rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  1.4338 +                                      aCookie->Path());
  1.4339 +    NS_ASSERT_SUCCESS(rv);
  1.4340 +
  1.4341 +    // Add our bound parameters to the array.
  1.4342 +    rv = aParamsArray->AddParams(params);
  1.4343 +    NS_ASSERT_SUCCESS(rv);
  1.4344 +  }
  1.4345 +}
  1.4346 +
  1.4347 +size_t
  1.4348 +nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  1.4349 +{
  1.4350 +  size_t n = aMallocSizeOf(this);
  1.4351 +
  1.4352 +  if (mDefaultDBState) {
  1.4353 +    n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
  1.4354 +  }
  1.4355 +  if (mPrivateDBState) {
  1.4356 +    n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
  1.4357 +  }
  1.4358 +
  1.4359 +  return n;
  1.4360 +}
  1.4361 +
  1.4362 +MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
  1.4363 +
  1.4364 +NS_IMETHODIMP
  1.4365 +nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
  1.4366 +                                nsISupports* aData)
  1.4367 +{
  1.4368 +  return MOZ_COLLECT_REPORT(
  1.4369 +    "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
  1.4370 +    SizeOfIncludingThis(CookieServiceMallocSizeOf),
  1.4371 +    "Memory used by the cookie service.");
  1.4372 +}

mercurial