netwerk/cookie/nsCookieService.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
parent 0
6474c204b198
child 7
129ffea94266
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set sw=2 ts=8 et tw=80 : */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "mozilla/Attributes.h"
     8 #include "mozilla/DebugOnly.h"
     9 #include "mozilla/Likely.h"
    11 #ifdef MOZ_LOGGING
    12 // this next define has to appear before the include of prlog.h
    13 #define FORCE_PR_LOG // Allow logging in the release build
    14 #endif
    16 #include "mozilla/net/CookieServiceChild.h"
    17 #include "mozilla/net/NeckoCommon.h"
    19 #include "nsCookieService.h"
    20 #include "nsIServiceManager.h"
    22 #include "nsIIOService.h"
    23 #include "nsIPrefBranch.h"
    24 #include "nsIPrefService.h"
    25 #include "nsICookiePermission.h"
    26 #include "nsIURI.h"
    27 #include "nsIURL.h"
    28 #include "nsIChannel.h"
    29 #include "nsIFile.h"
    30 #include "nsIObserverService.h"
    31 #include "nsILineInputStream.h"
    32 #include "nsIEffectiveTLDService.h"
    33 #include "nsIIDNService.h"
    34 #include "mozIThirdPartyUtil.h"
    36 #include "nsTArray.h"
    37 #include "nsCOMArray.h"
    38 #include "nsIMutableArray.h"
    39 #include "nsArrayEnumerator.h"
    40 #include "nsEnumeratorUtils.h"
    41 #include "nsAutoPtr.h"
    42 #include "nsReadableUtils.h"
    43 #include "nsCRT.h"
    44 #include "prprf.h"
    45 #include "nsNetUtil.h"
    46 #include "nsNetCID.h"
    47 #include "nsAppDirectoryServiceDefs.h"
    48 #include "nsNetCID.h"
    49 #include "mozilla/storage.h"
    50 #include "mozilla/AutoRestore.h"
    51 #include "mozilla/FileUtils.h"
    52 #include "mozilla/Telemetry.h"
    53 #include "nsIAppsService.h"
    54 #include "mozIApplication.h"
    55 #include "nsIConsoleService.h"
    57 using namespace mozilla;
    58 using namespace mozilla::net;
    60 // Create key from baseDomain that will access the default cookie namespace.
    61 // TODO: When we figure out what the API will look like for nsICookieManager{2}
    62 // on content processes (see bug 777620), change to use the appropriate app
    63 // namespace.  For now those IDLs aren't supported on child processes.
    64 #define DEFAULT_APP_KEY(baseDomain) \
    65         nsCookieKey(baseDomain, NECKO_NO_APP_ID, false)
    67 /******************************************************************************
    68  * nsCookieService impl:
    69  * useful types & constants
    70  ******************************************************************************/
    72 static nsCookieService *gCookieService;
    74 // XXX_hack. See bug 178993.
    75 // This is a hack to hide HttpOnly cookies from older browsers
    76 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
    78 #define COOKIES_FILE "cookies.sqlite"
    79 #define COOKIES_SCHEMA_VERSION 5
    81 // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
    82 // ReadCookieDBListener::HandleResult
    83 #define IDX_NAME 0
    84 #define IDX_VALUE 1
    85 #define IDX_HOST 2
    86 #define IDX_PATH 3
    87 #define IDX_EXPIRY 4
    88 #define IDX_LAST_ACCESSED 5
    89 #define IDX_CREATION_TIME 6
    90 #define IDX_SECURE 7
    91 #define IDX_HTTPONLY 8
    92 #define IDX_BASE_DOMAIN 9
    93 #define IDX_APP_ID 10
    94 #define IDX_BROWSER_ELEM 11
    96 static const int64_t kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
    97 static const int64_t kCookiePurgeAge =
    98   int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
   100 static const char kOldCookieFileName[] = "cookies.txt";
   102 #undef  LIMIT
   103 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
   105 #undef  ADD_TEN_PERCENT
   106 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
   108 // default limits for the cookie list. these can be tuned by the
   109 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
   110 static const uint32_t kMaxNumberOfCookies = 3000;
   111 static const uint32_t kMaxCookiesPerHost  = 150;
   112 static const uint32_t kMaxBytesPerCookie  = 4096;
   113 static const uint32_t kMaxBytesPerPath    = 1024;
   115 // behavior pref constants
   116 static const uint32_t BEHAVIOR_ACCEPT        = 0; // allow all cookies
   117 static const uint32_t BEHAVIOR_REJECTFOREIGN = 1; // reject all third-party cookies
   118 static const uint32_t BEHAVIOR_REJECT        = 2; // reject all cookies
   119 static const uint32_t BEHAVIOR_LIMITFOREIGN  = 3; // reject third-party cookies unless the
   120                                                   // eTLD already has at least one cookie
   122 // pref string constants
   123 static const char kPrefCookieBehavior[]     = "network.cookie.cookieBehavior";
   124 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
   125 static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
   126 static const char kPrefCookiePurgeAge[]     = "network.cookie.purgeAge";
   127 static const char kPrefThirdPartySession[]  = "network.cookie.thirdparty.sessionOnly";
   129 static void
   130 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
   131                      const nsCookieKey &aKey,
   132                      const nsCookie *aCookie);
   134 // struct for temporarily storing cookie attributes during header parsing
   135 struct nsCookieAttributes
   136 {
   137   nsAutoCString name;
   138   nsAutoCString value;
   139   nsAutoCString host;
   140   nsAutoCString path;
   141   nsAutoCString expires;
   142   nsAutoCString maxage;
   143   int64_t expiryTime;
   144   bool isSession;
   145   bool isSecure;
   146   bool isHttpOnly;
   147 };
   149 // stores the nsCookieEntry entryclass and an index into the cookie array
   150 // within that entryclass, for purposes of storing an iteration state that
   151 // points to a certain cookie.
   152 struct nsListIter
   153 {
   154   // default (non-initializing) constructor.
   155   nsListIter()
   156   {
   157   }
   159   // explicit constructor to a given iterator state with entryclass 'aEntry'
   160   // and index 'aIndex'.
   161   explicit
   162   nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
   163    : entry(aEntry)
   164    , index(aIndex)
   165   {
   166   }
   168   // get the nsCookie * the iterator currently points to.
   169   nsCookie * Cookie() const
   170   {
   171     return entry->GetCookies()[index];
   172   }
   174   nsCookieEntry            *entry;
   175   nsCookieEntry::IndexType  index;
   176 };
   178 /******************************************************************************
   179  * Cookie logging handlers
   180  * used for logging in nsCookieService
   181  ******************************************************************************/
   183 // logging handlers
   184 #ifdef MOZ_LOGGING
   185 // in order to do logging, the following environment variables need to be set:
   186 //
   187 //    set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies
   188 //    set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies
   189 //    set NSPR_LOG_FILE=cookie.log
   190 //
   191 #include "prlog.h"
   192 #endif
   194 // define logging macros for convenience
   195 #define SET_COOKIE true
   196 #define GET_COOKIE false
   198 #ifdef PR_LOGGING
   199 static PRLogModuleInfo *
   200 GetCookieLog()
   201 {
   202   static PRLogModuleInfo *sCookieLog;
   203   if (!sCookieLog)
   204     sCookieLog = PR_NewLogModule("cookie");
   205   return sCookieLog;
   206 }
   208 #define COOKIE_LOGFAILURE(a, b, c, d)    LogFailure(a, b, c, d)
   209 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
   211 #define COOKIE_LOGEVICTED(a, details)          \
   212   PR_BEGIN_MACRO                               \
   213   if (PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG))  \
   214       LogEvicted(a, details);                  \
   215   PR_END_MACRO
   217 #define COOKIE_LOGSTRING(lvl, fmt)   \
   218   PR_BEGIN_MACRO                     \
   219     PR_LOG(GetCookieLog(), lvl, fmt);  \
   220     PR_LOG(GetCookieLog(), lvl, ("\n")); \
   221   PR_END_MACRO
   223 static void
   224 LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
   225 {
   226   // if logging isn't enabled, return now to save cycles
   227   if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_WARNING))
   228     return;
   230   nsAutoCString spec;
   231   if (aHostURI)
   232     aHostURI->GetAsciiSpec(spec);
   234   PR_LOG(GetCookieLog(), PR_LOG_WARNING,
   235     ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
   236   PR_LOG(GetCookieLog(), PR_LOG_WARNING,("request URL: %s\n", spec.get()));
   237   if (aSetCookie)
   238     PR_LOG(GetCookieLog(), PR_LOG_WARNING,("cookie string: %s\n", aCookieString));
   240   PRExplodedTime explodedTime;
   241   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
   242   char timeString[40];
   243   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   245   PR_LOG(GetCookieLog(), PR_LOG_WARNING,("current time: %s", timeString));
   246   PR_LOG(GetCookieLog(), PR_LOG_WARNING,("rejected because %s\n", aReason));
   247   PR_LOG(GetCookieLog(), PR_LOG_WARNING,("\n"));
   248 }
   250 static void
   251 LogCookie(nsCookie *aCookie)
   252 {
   253   PRExplodedTime explodedTime;
   254   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
   255   char timeString[40];
   256   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   258   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("current time: %s", timeString));
   260   if (aCookie) {
   261     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("----------------\n"));
   262     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get()));
   263     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get()));
   264     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
   265     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("orighost: %s\n", aCookie->Origin().get()));
   266     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get()));
   268     PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
   269                    PR_GMTParameters, &explodedTime);
   270     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   271     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   272       ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
   274     PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
   275     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   276     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("created: %s", timeString));
   278     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
   279     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
   280   }
   281 }
   283 static void
   284 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
   285 {
   286   // if logging isn't enabled, return now to save cycles
   287   if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG)) {
   288     return;
   289   }
   291   nsAutoCString spec;
   292   if (aHostURI)
   293     aHostURI->GetAsciiSpec(spec);
   295   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   296     ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
   297   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
   298   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
   299   if (aSetCookie)
   300     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
   302   LogCookie(aCookie);
   304   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   305 }
   307 static void
   308 LogEvicted(nsCookie *aCookie, const char* details)
   309 {
   310   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
   311   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s\n", details));
   313   LogCookie(aCookie);
   315   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   316 }
   318 // inline wrappers to make passing in nsAFlatCStrings easier
   319 static inline void
   320 LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
   321 {
   322   LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
   323 }
   325 static inline void
   326 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
   327 {
   328   LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
   329 }
   331 #else
   332 #define COOKIE_LOGFAILURE(a, b, c, d)    PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   333 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   334 #define COOKIE_LOGEVICTED(a, b)          PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   335 #define COOKIE_LOGSTRING(a, b)           PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   336 #endif
   338 #ifdef DEBUG
   339 #define NS_ASSERT_SUCCESS(res)                                               \
   340   PR_BEGIN_MACRO                                                             \
   341   nsresult __rv = res; /* Do not evaluate |res| more than once! */           \
   342   if (NS_FAILED(__rv)) {                                                     \
   343     char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
   344                             #res, __rv);                                     \
   345     NS_ASSERTION(NS_SUCCEEDED(__rv), msg);                                   \
   346     PR_smprintf_free(msg);                                                   \
   347   }                                                                          \
   348   PR_END_MACRO
   349 #else
   350 #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   351 #endif
   353 /******************************************************************************
   354  * DBListenerErrorHandler impl:
   355  * Parent class for our async storage listeners that handles the logging of
   356  * errors.
   357  ******************************************************************************/
   358 class DBListenerErrorHandler : public mozIStorageStatementCallback
   359 {
   360 protected:
   361   DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
   362   nsRefPtr<DBState> mDBState;
   363   virtual const char *GetOpType() = 0;
   365 public:
   366   NS_IMETHOD HandleError(mozIStorageError* aError)
   367   {
   368     int32_t result = -1;
   369     aError->GetResult(&result);
   371 #ifdef PR_LOGGING
   372     nsAutoCString message;
   373     aError->GetMessage(message);
   374     COOKIE_LOGSTRING(PR_LOG_WARNING,
   375       ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
   376        "performing operation '%s' with message '%s'; rebuilding database.",
   377        result, GetOpType(), message.get()));
   378 #endif
   380     // Rebuild the database.
   381     gCookieService->HandleCorruptDB(mDBState);
   383     return NS_OK;
   384   }
   385 };
   387 /******************************************************************************
   388  * InsertCookieDBListener impl:
   389  * mozIStorageStatementCallback used to track asynchronous insertion operations.
   390  ******************************************************************************/
   391 class InsertCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   392 {
   393 protected:
   394   virtual const char *GetOpType() { return "INSERT"; }
   396 public:
   397   NS_DECL_ISUPPORTS
   399   InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   400   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   401   {
   402     NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
   403     return NS_OK;
   404   }
   405   NS_IMETHOD HandleCompletion(uint16_t aReason)
   406   {
   407     // If we were rebuilding the db and we succeeded, make our corruptFlag say
   408     // so.
   409     if (mDBState->corruptFlag == DBState::REBUILDING &&
   410         aReason == mozIStorageStatementCallback::REASON_FINISHED) {
   411       COOKIE_LOGSTRING(PR_LOG_DEBUG,
   412         ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
   413       mDBState->corruptFlag = DBState::OK;
   414     }
   415     return NS_OK;
   416   }
   417 };
   419 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
   421 /******************************************************************************
   422  * UpdateCookieDBListener impl:
   423  * mozIStorageStatementCallback used to track asynchronous update operations.
   424  ******************************************************************************/
   425 class UpdateCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   426 {
   427 protected:
   428   virtual const char *GetOpType() { return "UPDATE"; }
   430 public:
   431   NS_DECL_ISUPPORTS
   433   UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   434   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   435   {
   436     NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
   437     return NS_OK;
   438   }
   439   NS_IMETHOD HandleCompletion(uint16_t aReason)
   440   {
   441     return NS_OK;
   442   }
   443 };
   445 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
   447 /******************************************************************************
   448  * RemoveCookieDBListener impl:
   449  * mozIStorageStatementCallback used to track asynchronous removal operations.
   450  ******************************************************************************/
   451 class RemoveCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   452 {
   453 protected:
   454   virtual const char *GetOpType() { return "REMOVE"; }
   456 public:
   457   NS_DECL_ISUPPORTS
   459   RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   460   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   461   {
   462     NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
   463     return NS_OK;
   464   }
   465   NS_IMETHOD HandleCompletion(uint16_t aReason)
   466   {
   467     return NS_OK;
   468   }
   469 };
   471 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
   473 /******************************************************************************
   474  * ReadCookieDBListener impl:
   475  * mozIStorageStatementCallback used to track asynchronous removal operations.
   476  ******************************************************************************/
   477 class ReadCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   478 {
   479 protected:
   480   virtual const char *GetOpType() { return "READ"; }
   481   bool mCanceled;
   483 public:
   484   NS_DECL_ISUPPORTS
   486   ReadCookieDBListener(DBState* dbState)
   487     : DBListenerErrorHandler(dbState)
   488     , mCanceled(false)
   489   {
   490   }
   492   void Cancel() { mCanceled = true; }
   494   NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
   495   {
   496     nsCOMPtr<mozIStorageRow> row;
   498     while (1) {
   499       DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
   500       NS_ASSERT_SUCCESS(rv);
   502       if (!row)
   503         break;
   505       CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
   506       row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
   507       tuple->key.mAppId = static_cast<uint32_t>(row->AsInt32(IDX_APP_ID));
   508       tuple->key.mInBrowserElement = static_cast<bool>(row->AsInt32(IDX_BROWSER_ELEM));
   509       tuple->cookie = gCookieService->GetCookieFromRow(row);
   510     }
   512     return NS_OK;
   513   }
   514   NS_IMETHOD HandleCompletion(uint16_t aReason)
   515   {
   516     // Process the completion of the read operation. If we have been canceled,
   517     // we cannot assume that the cookieservice still has an open connection
   518     // or that it even refers to the same database, so we must return early.
   519     // Conversely, the cookieservice guarantees that if we have not been
   520     // canceled, the database connection is still alive and we can safely
   521     // operate on it.
   523     if (mCanceled) {
   524       // We may receive a REASON_FINISHED after being canceled;
   525       // tweak the reason accordingly.
   526       aReason = mozIStorageStatementCallback::REASON_CANCELED;
   527     }
   529     switch (aReason) {
   530     case mozIStorageStatementCallback::REASON_FINISHED:
   531       gCookieService->AsyncReadComplete();
   532       break;
   533     case mozIStorageStatementCallback::REASON_CANCELED:
   534       // Nothing more to do here. The partially read data has already been
   535       // thrown away.
   536       COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled"));
   537       break;
   538     case mozIStorageStatementCallback::REASON_ERROR:
   539       // Nothing more to do here. DBListenerErrorHandler::HandleError()
   540       // can handle it.
   541       COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error"));
   542       break;
   543     default:
   544       NS_NOTREACHED("invalid reason");
   545     }
   546     return NS_OK;
   547   }
   548 };
   550 NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
   552 /******************************************************************************
   553  * CloseCookieDBListener imp:
   554  * Static mozIStorageCompletionCallback used to notify when the database is
   555  * successfully closed.
   556  ******************************************************************************/
   557 class CloseCookieDBListener MOZ_FINAL :  public mozIStorageCompletionCallback
   558 {
   559 public:
   560   CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
   561   nsRefPtr<DBState> mDBState;
   562   NS_DECL_ISUPPORTS
   564   NS_IMETHOD Complete(nsresult, nsISupports*)
   565   {
   566     gCookieService->HandleDBClosed(mDBState);
   567     return NS_OK;
   568   }
   569 };
   571 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
   573 namespace {
   575 class AppClearDataObserver MOZ_FINAL : public nsIObserver {
   576 public:
   577   NS_DECL_ISUPPORTS
   579   // nsIObserver implementation.
   580   NS_IMETHODIMP
   581   Observe(nsISupports *aSubject, const char *aTopic, const char16_t *data)
   582   {
   583     MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA));
   585     uint32_t appId = NECKO_UNKNOWN_APP_ID;
   586     bool browserOnly = false;
   587     nsresult rv = NS_GetAppInfoFromClearDataNotification(aSubject, &appId,
   588                                                          &browserOnly);
   589     NS_ENSURE_SUCCESS(rv, rv);
   591     nsCOMPtr<nsICookieManager2> cookieManager
   592       = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
   593     MOZ_ASSERT(cookieManager);
   594     return cookieManager->RemoveCookiesForApp(appId, browserOnly);
   595   }
   596 };
   598 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
   600 } // anonymous namespace
   602 size_t
   603 nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   604 {
   605   return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   606 }
   608 size_t
   609 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   610 {
   611   size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
   613   amount += mCookies.SizeOfExcludingThis(aMallocSizeOf);
   614   for (uint32_t i = 0; i < mCookies.Length(); ++i) {
   615     amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
   616   }
   618   return amount;
   619 }
   621 static size_t
   622 HostTableEntrySizeOfExcludingThis(nsCookieEntry *aEntry,
   623                                   MallocSizeOf aMallocSizeOf,
   624                                   void *arg)
   625 {
   626   return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   627 }
   629 size_t
   630 CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   631 {
   632   size_t amount = 0;
   634   amount += key.SizeOfExcludingThis(aMallocSizeOf);
   635   amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
   637   return amount;
   638 }
   640 static size_t
   641 ReadSetEntrySizeOfExcludingThis(nsCookieKey *aEntry,
   642                                 MallocSizeOf aMallocSizeOf,
   643                                 void *)
   644 {
   645   return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   646 }
   648 size_t
   649 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   650 {
   651   size_t amount = 0;
   653   amount += aMallocSizeOf(this);
   654   amount += hostTable.SizeOfExcludingThis(HostTableEntrySizeOfExcludingThis,
   655                                           aMallocSizeOf);
   656   amount += hostArray.SizeOfExcludingThis(aMallocSizeOf);
   657   for (uint32_t i = 0; i < hostArray.Length(); ++i) {
   658     amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
   659   }
   660   amount += readSet.SizeOfExcludingThis(ReadSetEntrySizeOfExcludingThis,
   661                                         aMallocSizeOf);
   663   return amount;
   664 }
   666 /******************************************************************************
   667  * nsCookieService impl:
   668  * singleton instance ctor/dtor methods
   669  ******************************************************************************/
   671 nsICookieService*
   672 nsCookieService::GetXPCOMSingleton()
   673 {
   674   if (IsNeckoChild())
   675     return CookieServiceChild::GetSingleton();
   677   return GetSingleton();
   678 }
   680 nsCookieService*
   681 nsCookieService::GetSingleton()
   682 {
   683   NS_ASSERTION(!IsNeckoChild(), "not a parent process");
   685   if (gCookieService) {
   686     NS_ADDREF(gCookieService);
   687     return gCookieService;
   688   }
   690   // Create a new singleton nsCookieService.
   691   // We AddRef only once since XPCOM has rules about the ordering of module
   692   // teardowns - by the time our module destructor is called, it's too late to
   693   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
   694   // cycles have already been completed and would result in serious leaks.
   695   // See bug 209571.
   696   gCookieService = new nsCookieService();
   697   if (gCookieService) {
   698     NS_ADDREF(gCookieService);
   699     if (NS_FAILED(gCookieService->Init())) {
   700       NS_RELEASE(gCookieService);
   701     }
   702   }
   704   return gCookieService;
   705 }
   707 /* static */ void
   708 nsCookieService::AppClearDataObserverInit()
   709 {
   710   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   711   nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
   712   observerService->AddObserver(obs, TOPIC_WEB_APP_CLEAR_DATA,
   713                                /* holdsWeak= */ false);
   714 }
   716 /******************************************************************************
   717  * nsCookieService impl:
   718  * public methods
   719  ******************************************************************************/
   721 NS_IMPL_ISUPPORTS(nsCookieService,
   722                   nsICookieService,
   723                   nsICookieManager,
   724                   nsICookieManager2,
   725                   nsIObserver,
   726                   nsISupportsWeakReference,
   727                   nsIMemoryReporter)
   729 nsCookieService::nsCookieService()
   730  : mDBState(nullptr)
   731  , mCookieBehavior(BEHAVIOR_ACCEPT)
   732  , mThirdPartySession(false)
   733  , mMaxNumberOfCookies(kMaxNumberOfCookies)
   734  , mMaxCookiesPerHost(kMaxCookiesPerHost)
   735  , mCookiePurgeAge(kCookiePurgeAge)
   736 {
   737 }
   739 nsresult
   740 nsCookieService::Init()
   741 {
   742   nsresult rv;
   743   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
   744   NS_ENSURE_SUCCESS(rv, rv);
   746   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
   747   NS_ENSURE_SUCCESS(rv, rv);
   749   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   750   NS_ENSURE_SUCCESS(rv, rv);
   752   // init our pref and observer
   753   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   754   if (prefBranch) {
   755     prefBranch->AddObserver(kPrefCookieBehavior,     this, true);
   756     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
   757     prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, true);
   758     prefBranch->AddObserver(kPrefCookiePurgeAge,     this, true);
   759     prefBranch->AddObserver(kPrefThirdPartySession,  this, true);
   760     PrefChanged(prefBranch);
   761   }
   763   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
   764   NS_ENSURE_SUCCESS(rv, rv);
   766   // Init our default, and possibly private DBStates.
   767   InitDBStates();
   769   RegisterWeakMemoryReporter(this);
   771   mObserverService = mozilla::services::GetObserverService();
   772   NS_ENSURE_STATE(mObserverService);
   773   mObserverService->AddObserver(this, "profile-before-change", true);
   774   mObserverService->AddObserver(this, "profile-do-change", true);
   775   mObserverService->AddObserver(this, "last-pb-context-exited", true);
   777   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
   778   if (!mPermissionService) {
   779     NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
   780     COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
   781   }
   783   return NS_OK;
   784 }
   786 void
   787 nsCookieService::InitDBStates()
   788 {
   789   NS_ASSERTION(!mDBState, "already have a DBState");
   790   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
   791   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
   793   // Create a new default DBState and set our current one.
   794   mDefaultDBState = new DBState();
   795   mDBState = mDefaultDBState;
   797   mPrivateDBState = new DBState();
   799   // Get our cookie file.
   800   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   801     getter_AddRefs(mDefaultDBState->cookieFile));
   802   if (NS_FAILED(rv)) {
   803     // We've already set up our DBStates appropriately; nothing more to do.
   804     COOKIE_LOGSTRING(PR_LOG_WARNING,
   805       ("InitDBStates(): couldn't get cookie file"));
   806     return;
   807   }
   808   mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
   810   // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
   811   // do so.
   812   OpenDBResult result = TryInitDB(false);
   813   if (result == RESULT_RETRY) {
   814     // Database may be corrupt. Synchronously close the connection, clean up the
   815     // default DBState, and try again.
   816     COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()"));
   817     CleanupCachedStatements();
   818     CleanupDefaultDBConnection();
   819     result = TryInitDB(true);
   820     if (result == RESULT_RETRY) {
   821       // We're done. Change the code to failure so we clean up below.
   822       result = RESULT_FAILURE;
   823     }
   824   }
   826   if (result == RESULT_FAILURE) {
   827     COOKIE_LOGSTRING(PR_LOG_WARNING,
   828       ("InitDBStates(): TryInitDB() failed, closing connection"));
   830     // Connection failure is unrecoverable. Clean up our connection. We can run
   831     // fine without persistent storage -- e.g. if there's no profile.
   832     CleanupCachedStatements();
   833     CleanupDefaultDBConnection();
   834   }
   835 }
   837 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
   838  * move the existing database file out of the way and create a new one.
   839  *
   840  * @returns RESULT_OK if opening or creating the database succeeded;
   841  *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
   842  *          other failure occurred that might be resolved by recreating the
   843  *          database; or RESULT_FAILED if there was an unrecoverable error and
   844  *          we must run without a database.
   845  *
   846  * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
   847  * cleanup of the default DBState.
   848  */
   849 OpenDBResult
   850 nsCookieService::TryInitDB(bool aRecreateDB)
   851 {
   852   NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
   853   NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
   854   NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
   855   NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
   857   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
   858   // want to delete it outright, since it may be useful for debugging purposes,
   859   // so we move it out of the way.
   860   nsresult rv;
   861   if (aRecreateDB) {
   862     nsCOMPtr<nsIFile> backupFile;
   863     mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
   864     rv = backupFile->MoveToNative(nullptr,
   865       NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
   866     NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
   867   }
   869   // This block provides scope for the Telemetry AutoTimer
   870   {
   871     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
   872       telemetry;
   873     ReadAheadFile(mDefaultDBState->cookieFile);
   875     // open a connection to the cookie database, and only cache our connection
   876     // and statements upon success. The connection is opened unshared to eliminate
   877     // cache contention between the main and background threads.
   878     rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
   879       getter_AddRefs(mDefaultDBState->dbConn));
   880     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   881   }
   883   // Set up our listeners.
   884   mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
   885   mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
   886   mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
   887   mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
   889   // Grow cookie db in 512KB increments
   890   mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
   892   bool tableExists = false;
   893   mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
   894     &tableExists);
   895   if (!tableExists) {
   896     rv = CreateTable();
   897     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   899   } else {
   900     // table already exists; check the schema version before reading
   901     int32_t dbSchemaVersion;
   902     rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
   903     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   905     // Start a transaction for the whole migration block.
   906     mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
   908     switch (dbSchemaVersion) {
   909     // Upgrading.
   910     // Every time you increment the database schema, you need to implement
   911     // the upgrading code from the previous version to the new one. If migration
   912     // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
   913     // the original database will be saved, in the hopes that we might one day
   914     // see it and fix it.
   915     case 1:
   916       {
   917         // Add the lastAccessed column to the table.
   918         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   919           "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
   920         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   921       }
   922       // Fall through to the next upgrade.
   924     case 2:
   925       {
   926         // Add the baseDomain column and index to the table.
   927         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   928           "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
   929         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   931         // Compute the baseDomains for the table. This must be done eagerly
   932         // otherwise we won't be able to synchronously read in individual
   933         // domains on demand.
   934         const int64_t SCHEMA2_IDX_ID  =  0;
   935         const int64_t SCHEMA2_IDX_HOST = 1;
   936         nsCOMPtr<mozIStorageStatement> select;
   937         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   938           "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
   939         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   941         nsCOMPtr<mozIStorageStatement> update;
   942         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   943           "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
   944           getter_AddRefs(update));
   945         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   947         nsCString baseDomain, host;
   948         bool hasResult;
   949         while (1) {
   950           rv = select->ExecuteStep(&hasResult);
   951           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   953           if (!hasResult)
   954             break;
   956           int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
   957           select->GetUTF8String(SCHEMA2_IDX_HOST, host);
   959           rv = GetBaseDomainFromHost(host, baseDomain);
   960           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   962           mozStorageStatementScoper scoper(update);
   964           rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
   965                                             baseDomain);
   966           NS_ASSERT_SUCCESS(rv);
   967           rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
   968                                        id);
   969           NS_ASSERT_SUCCESS(rv);
   971           rv = update->ExecuteStep(&hasResult);
   972           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   973         }
   975         // Create an index on baseDomain.
   976         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   977           "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
   978         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   979       }
   980       // Fall through to the next upgrade.
   982     case 3:
   983       {
   984         // Add the creationTime column to the table, and create a unique index
   985         // on (name, host, path). Before we do this, we have to purge the table
   986         // of expired cookies such that we know that the (name, host, path)
   987         // index is truly unique -- otherwise we can't create the index. Note
   988         // that we can't just execute a statement to delete all rows where the
   989         // expiry column is in the past -- doing so would rely on the clock
   990         // (both now and when previous cookies were set) being monotonic.
   992         // Select the whole table, and order by the fields we're interested in.
   993         // This means we can simply do a linear traversal of the results and
   994         // check for duplicates as we go.
   995         const int64_t SCHEMA3_IDX_ID =   0;
   996         const int64_t SCHEMA3_IDX_NAME = 1;
   997         const int64_t SCHEMA3_IDX_HOST = 2;
   998         const int64_t SCHEMA3_IDX_PATH = 3;
   999         nsCOMPtr<mozIStorageStatement> select;
  1000         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1001           "SELECT id, name, host, path FROM moz_cookies "
  1002             "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
  1003           getter_AddRefs(select));
  1004         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1006         nsCOMPtr<mozIStorageStatement> deleteExpired;
  1007         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1008           "DELETE FROM moz_cookies WHERE id = :id"),
  1009           getter_AddRefs(deleteExpired));
  1010         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1012         // Read the first row.
  1013         bool hasResult;
  1014         rv = select->ExecuteStep(&hasResult);
  1015         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1017         if (hasResult) {
  1018           nsCString name1, host1, path1;
  1019           int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
  1020           select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
  1021           select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
  1022           select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
  1024           nsCString name2, host2, path2;
  1025           while (1) {
  1026             // Read the second row.
  1027             rv = select->ExecuteStep(&hasResult);
  1028             NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1030             if (!hasResult)
  1031               break;
  1033             int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
  1034             select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
  1035             select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
  1036             select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
  1038             // If the two rows match in (name, host, path), we know the earlier
  1039             // row has an earlier expiry time. Delete it.
  1040             if (name1 == name2 && host1 == host2 && path1 == path2) {
  1041               mozStorageStatementScoper scoper(deleteExpired);
  1043               rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
  1044                 id1);
  1045               NS_ASSERT_SUCCESS(rv);
  1047               rv = deleteExpired->ExecuteStep(&hasResult);
  1048               NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1051             // Make the second row the first for the next iteration.
  1052             name1 = name2;
  1053             host1 = host2;
  1054             path1 = path2;
  1055             id1 = id2;
  1059         // Add the creationTime column to the table.
  1060         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1061           "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
  1062         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1064         // Copy the id of each row into the new creationTime column.
  1065         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1066           "UPDATE moz_cookies SET creationTime = "
  1067             "(SELECT id WHERE id = moz_cookies.id)"));
  1068         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1070         // Create a unique index on (name, host, path) to allow fast lookup.
  1071         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1072           "CREATE UNIQUE INDEX moz_uniqueid "
  1073           "ON moz_cookies (name, host, path)"));
  1074         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1076       // Fall through to the next upgrade.
  1078     case 4:
  1080         // We need to add appId/inBrowserElement, plus change a constraint on
  1081         // the table (unique entries now include appId/inBrowserElement):
  1082         // this requires creating a new table and copying the data to it.  We
  1083         // then rename the new table to the old name.
  1084         //
  1085         // Why we made this change: appId/inBrowserElement allow "cookie jars"
  1086         // for Firefox OS. We create a separate cookie namespace per {appId,
  1087         // inBrowserElement}.  When upgrading, we convert existing cookies
  1088         // (which imply we're on desktop/mobile) to use {0, false}, as that is
  1089         // the only namespace used by a non-Firefox-OS implementation.
  1091         // Rename existing table
  1092         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1093           "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
  1094         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1096         // Drop existing index (CreateTable will create new one for new table)
  1097         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1098           "DROP INDEX moz_basedomain"));
  1099         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1101         // Create new table (with new fields and new unique constraint)
  1102         rv = CreateTable();
  1103         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1105         // Copy data from old table, using appId/inBrowser=0 for existing rows
  1106         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1107           "INSERT INTO moz_cookies "
  1108           "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
  1109           " lastAccessed, creationTime, isSecure, isHttpOnly) "
  1110           "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
  1111           " lastAccessed, creationTime, isSecure, isHttpOnly "
  1112           "FROM moz_cookies_old"));
  1113         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1115         // Drop old table
  1116         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1117           "DROP TABLE moz_cookies_old"));
  1118         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1120         COOKIE_LOGSTRING(PR_LOG_DEBUG, 
  1121           ("Upgraded database to schema version 5"));
  1124       // No more upgrades. Update the schema version.
  1125       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1126       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1128     case COOKIES_SCHEMA_VERSION:
  1129       break;
  1131     case 0:
  1133         NS_WARNING("couldn't get schema version!");
  1135         // the table may be usable; someone might've just clobbered the schema
  1136         // version. we can treat this case like a downgrade using the codepath
  1137         // below, by verifying the columns we care about are all there. for now,
  1138         // re-set the schema version in the db, in case the checks succeed (if
  1139         // they don't, we're dropping the table anyway).
  1140         rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1141         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1143       // fall through to downgrade check
  1145     // downgrading.
  1146     // if columns have been added to the table, we can still use the ones we
  1147     // understand safely. if columns have been deleted or altered, just
  1148     // blow away the table and start from scratch! if you change the way
  1149     // a column is interpreted, make sure you also change its name so this
  1150     // check will catch it.
  1151     default:
  1153         // check if all the expected columns exist
  1154         nsCOMPtr<mozIStorageStatement> stmt;
  1155         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1156           "SELECT "
  1157             "id, "
  1158             "baseDomain, "
  1159             "appId, "
  1160             "inBrowserElement, "
  1161             "name, "
  1162             "value, "
  1163             "host, "
  1164             "path, "
  1165             "expiry, "
  1166             "lastAccessed, "
  1167             "creationTime, "
  1168             "isSecure, "
  1169             "isHttpOnly "
  1170           "FROM moz_cookies"), getter_AddRefs(stmt));
  1171         if (NS_SUCCEEDED(rv))
  1172           break;
  1174         // our columns aren't there - drop the table!
  1175         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1176           "DROP TABLE moz_cookies"));
  1177         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1179         rv = CreateTable();
  1180         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1182       break;
  1186   // make operations on the table asynchronous, for performance
  1187   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1188     "PRAGMA synchronous = OFF"));
  1190   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
  1191   // 16 pages (around 500KB).
  1192   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1193     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
  1194   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1195     "PRAGMA wal_autocheckpoint = 16"));
  1197   // cache frequently used statements (for insertion, deletion, and updating)
  1198   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1199     "INSERT INTO moz_cookies ("
  1200       "baseDomain, "
  1201       "appId, "
  1202       "inBrowserElement, "
  1203       "name, "
  1204       "value, "
  1205       "host, "
  1206       "path, "
  1207       "expiry, "
  1208       "lastAccessed, "
  1209       "creationTime, "
  1210       "isSecure, "
  1211       "isHttpOnly"
  1212     ") VALUES ("
  1213       ":baseDomain, "
  1214       ":appId, "
  1215       ":inBrowserElement, "
  1216       ":name, "
  1217       ":value, "
  1218       ":host, "
  1219       ":path, "
  1220       ":expiry, "
  1221       ":lastAccessed, "
  1222       ":creationTime, "
  1223       ":isSecure, "
  1224       ":isHttpOnly"
  1225     ")"),
  1226     getter_AddRefs(mDefaultDBState->stmtInsert));
  1227   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1229   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1230     "DELETE FROM moz_cookies "
  1231     "WHERE name = :name AND host = :host AND path = :path"),
  1232     getter_AddRefs(mDefaultDBState->stmtDelete));
  1233   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1235   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1236     "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
  1237     "WHERE name = :name AND host = :host AND path = :path"),
  1238     getter_AddRefs(mDefaultDBState->stmtUpdate));
  1239   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1241   // if we deleted a corrupt db, don't attempt to import - return now
  1242   if (aRecreateDB)
  1243     return RESULT_OK;
  1245   // check whether to import or just read in the db
  1246   if (tableExists)
  1247     return Read();
  1249   nsCOMPtr<nsIFile> oldCookieFile;
  1250   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
  1251     getter_AddRefs(oldCookieFile));
  1252   if (NS_FAILED(rv)) return RESULT_OK;
  1254   // Import cookies, and clean up the old file regardless of success or failure.
  1255   // Note that we have to switch out our DBState temporarily, in case we're in
  1256   // private browsing mode; otherwise ImportCookies() won't be happy.
  1257   DBState* initialState = mDBState;
  1258   mDBState = mDefaultDBState;
  1259   oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
  1260   ImportCookies(oldCookieFile);
  1261   oldCookieFile->Remove(false);
  1262   mDBState = initialState;
  1264   return RESULT_OK;
  1267 // Sets the schema version and creates the moz_cookies table.
  1268 nsresult
  1269 nsCookieService::CreateTable()
  1271   // Set the schema version, before creating the table.
  1272   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
  1273     COOKIES_SCHEMA_VERSION);
  1274   if (NS_FAILED(rv)) return rv;
  1276   // Create the table.  We default appId/inBrowserElement to 0: this is so if
  1277   // users revert to an older Firefox version that doesn't know about these
  1278   // fields, any cookies set will still work once they upgrade back.
  1279   rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1280     "CREATE TABLE moz_cookies ("
  1281       "id INTEGER PRIMARY KEY, "
  1282       "baseDomain TEXT, "
  1283       "appId INTEGER DEFAULT 0, "
  1284       "inBrowserElement INTEGER DEFAULT 0, "
  1285       "name TEXT, "
  1286       "value TEXT, "
  1287       "host TEXT, "
  1288       "path TEXT, "
  1289       "expiry INTEGER, "
  1290       "lastAccessed INTEGER, "
  1291       "creationTime INTEGER, "
  1292       "isSecure INTEGER, "
  1293       "isHttpOnly INTEGER, "
  1294       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
  1295     ")"));
  1296   if (NS_FAILED(rv)) return rv;
  1298   // Create an index on baseDomain.
  1299   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1300     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
  1301                                                 "appId, "
  1302                                                 "inBrowserElement)"));
  1305 void
  1306 nsCookieService::CloseDBStates()
  1308   // Null out our private and pointer DBStates regardless.
  1309   mPrivateDBState = nullptr;
  1310   mDBState = nullptr;
  1312   // If we don't have a default DBState, we're done.
  1313   if (!mDefaultDBState)
  1314     return;
  1316   // Cleanup cached statements before we can close anything.
  1317   CleanupCachedStatements();
  1319   if (mDefaultDBState->dbConn) {
  1320     // Cancel any pending read. No further results will be received by our
  1321     // read listener.
  1322     if (mDefaultDBState->pendingRead) {
  1323       CancelAsyncRead(true);
  1326     // Asynchronously close the connection. We will null it below.
  1327     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1330   CleanupDefaultDBConnection();
  1332   mDefaultDBState = nullptr;
  1335 // Null out the statements.
  1336 // This must be done before closing the connection.
  1337 void
  1338 nsCookieService::CleanupCachedStatements()
  1340   mDefaultDBState->stmtInsert = nullptr;
  1341   mDefaultDBState->stmtDelete = nullptr;
  1342   mDefaultDBState->stmtUpdate = nullptr;
  1345 // Null out the listeners, and the database connection itself. This
  1346 // will not null out the statements, cancel a pending read or
  1347 // asynchronously close the connection -- these must be done
  1348 // beforehand if necessary.
  1349 void
  1350 nsCookieService::CleanupDefaultDBConnection()
  1352   MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
  1353   MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
  1354   MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
  1356   // Null out the database connections. If 'dbConn' has not been used for any
  1357   // asynchronous operations yet, this will synchronously close it; otherwise,
  1358   // it's expected that the caller has performed an AsyncClose prior.
  1359   mDefaultDBState->dbConn = nullptr;
  1360   mDefaultDBState->syncConn = nullptr;
  1362   // Manually null out our listeners. This is necessary because they hold a
  1363   // strong ref to the DBState itself. They'll stay alive until whatever
  1364   // statements are still executing complete.
  1365   mDefaultDBState->readListener = nullptr;
  1366   mDefaultDBState->insertListener = nullptr;
  1367   mDefaultDBState->updateListener = nullptr;
  1368   mDefaultDBState->removeListener = nullptr;
  1369   mDefaultDBState->closeListener = nullptr;
  1372 void
  1373 nsCookieService::HandleDBClosed(DBState* aDBState)
  1375   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1376     ("HandleDBClosed(): DBState %x closed", aDBState));
  1378   switch (aDBState->corruptFlag) {
  1379   case DBState::OK: {
  1380     // Database is healthy. Notify of closure.
  1381     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1382     break;
  1384   case DBState::CLOSING_FOR_REBUILD: {
  1385     // Our close finished. Start the rebuild, and notify of db closure later.
  1386     RebuildCorruptDB(aDBState);
  1387     break;
  1389   case DBState::REBUILDING: {
  1390     // We encountered an error during rebuild, closed the database, and now
  1391     // here we are. We already have a 'cookies.sqlite.bak' from the original
  1392     // dead database; we don't want to overwrite it, so let's move this one to
  1393     // 'cookies.sqlite.bak-rebuild'.
  1394     nsCOMPtr<nsIFile> backupFile;
  1395     aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
  1396     nsresult rv = backupFile->MoveToNative(nullptr,
  1397       NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
  1399     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1400       ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
  1401        "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
  1402     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1403     break;
  1408 void
  1409 nsCookieService::HandleCorruptDB(DBState* aDBState)
  1411   if (mDefaultDBState != aDBState) {
  1412     // We've either closed the state or we've switched profiles. It's getting
  1413     // a bit late to rebuild -- bail instead.
  1414     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1415       ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
  1416     return;
  1419   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1420     ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
  1421       aDBState->corruptFlag));
  1423   // Mark the database corrupt, so the close listener can begin reconstructing
  1424   // it.
  1425   switch (mDefaultDBState->corruptFlag) {
  1426   case DBState::OK: {
  1427     // Move to 'closing' state.
  1428     mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
  1430     // Cancel any pending read and close the database. If we do have an
  1431     // in-flight read we want to throw away all the results so far -- we have no
  1432     // idea how consistent the database is. Note that we may have already
  1433     // canceled the read but not emptied our readSet; do so now.
  1434     mDefaultDBState->readSet.Clear();
  1435     if (mDefaultDBState->pendingRead) {
  1436       CancelAsyncRead(true);
  1437       mDefaultDBState->syncConn = nullptr;
  1440     CleanupCachedStatements();
  1441     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1442     CleanupDefaultDBConnection();
  1443     break;
  1445   case DBState::CLOSING_FOR_REBUILD: {
  1446     // We had an error while waiting for close completion. That's OK, just
  1447     // ignore it -- we're rebuilding anyway.
  1448     return;
  1450   case DBState::REBUILDING: {
  1451     // We had an error while rebuilding the DB. Game over. Close the database
  1452     // and let the close handler do nothing; then we'll move it out of the way.
  1453     CleanupCachedStatements();
  1454     if (mDefaultDBState->dbConn) {
  1455       mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1457     CleanupDefaultDBConnection();
  1458     break;
  1463 static PLDHashOperator
  1464 RebuildDBCallback(nsCookieEntry *aEntry,
  1465                   void          *aArg)
  1467   mozIStorageBindingParamsArray* paramsArray =
  1468     static_cast<mozIStorageBindingParamsArray*>(aArg);
  1470   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1471   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1472     nsCookie* cookie = cookies[i];
  1474     if (!cookie->IsSession()) {
  1475       bindCookieParameters(paramsArray, aEntry, cookie);
  1479   return PL_DHASH_NEXT;
  1482 void
  1483 nsCookieService::RebuildCorruptDB(DBState* aDBState)
  1485   NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
  1486   NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
  1487     "should be in CLOSING_FOR_REBUILD state");
  1489   aDBState->corruptFlag = DBState::REBUILDING;
  1491   if (mDefaultDBState != aDBState) {
  1492     // We've either closed the state or we've switched profiles. It's getting
  1493     // a bit late to rebuild -- bail instead. In any case, we were waiting
  1494     // on rebuild completion to notify of the db closure, which won't happen --
  1495     // do so now.
  1496     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1497       ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
  1498     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1499     return;
  1502   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1503     ("RebuildCorruptDB(): creating new database"));
  1505   // The database has been closed, and we're ready to rebuild. Open a
  1506   // connection.
  1507   OpenDBResult result = TryInitDB(true);
  1508   if (result != RESULT_OK) {
  1509     // We're done. Reset our DB connection and statements, and notify of
  1510     // closure.
  1511     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1512       ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
  1513     CleanupCachedStatements();
  1514     CleanupDefaultDBConnection();
  1515     mDefaultDBState->corruptFlag = DBState::OK;
  1516     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1517     return;
  1520   // Notify observers that we're beginning the rebuild.
  1521   mObserverService->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
  1523   // Enumerate the hash, and add cookies to the params array.
  1524   mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
  1525   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1526   stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1527   aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get());
  1529   // Make sure we've got something to write. If we don't, we're done.
  1530   uint32_t length;
  1531   paramsArray->GetLength(&length);
  1532   if (length == 0) {
  1533     COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1534       ("RebuildCorruptDB(): nothing to write, rebuild complete"));
  1535     mDefaultDBState->corruptFlag = DBState::OK;
  1536     return;
  1539   // Execute the statement. If any errors crop up, we won't try again.
  1540   DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1541   NS_ASSERT_SUCCESS(rv);
  1542   nsCOMPtr<mozIStoragePendingStatement> handle;
  1543   rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
  1544   NS_ASSERT_SUCCESS(rv);
  1547 nsCookieService::~nsCookieService()
  1549   CloseDBStates();
  1551   UnregisterWeakMemoryReporter(this);
  1553   gCookieService = nullptr;
  1556 NS_IMETHODIMP
  1557 nsCookieService::Observe(nsISupports     *aSubject,
  1558                          const char      *aTopic,
  1559                          const char16_t *aData)
  1561   // check the topic
  1562   if (!strcmp(aTopic, "profile-before-change")) {
  1563     // The profile is about to change,
  1564     // or is going away because the application is shutting down.
  1565     if (mDBState && mDBState->dbConn &&
  1566         !nsCRT::strcmp(aData, MOZ_UTF16("shutdown-cleanse"))) {
  1567       // Clear the cookie db if we're in the default DBState.
  1568       RemoveAll();
  1571     // Close the default DB connection and null out our DBStates before
  1572     // changing.
  1573     CloseDBStates();
  1575   } else if (!strcmp(aTopic, "profile-do-change")) {
  1576     NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
  1577     NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
  1579     // the profile has already changed; init the db from the new location.
  1580     // if we are in the private browsing state, however, we do not want to read
  1581     // data into it - we should instead put it into the default state, so it's
  1582     // ready for us if and when we switch back to it.
  1583     InitDBStates();
  1585   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
  1586     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
  1587     if (prefBranch)
  1588       PrefChanged(prefBranch);
  1590   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
  1591     // Flush all the cookies stored by private browsing contexts
  1592     mPrivateDBState = new DBState();
  1596   return NS_OK;
  1599 NS_IMETHODIMP
  1600 nsCookieService::GetCookieString(nsIURI     *aHostURI,
  1601                                  nsIChannel *aChannel,
  1602                                  char       **aCookie)
  1604   return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
  1607 NS_IMETHODIMP
  1608 nsCookieService::GetCookieStringFromHttp(nsIURI     *aHostURI,
  1609                                          nsIURI     *aFirstURI,
  1610                                          nsIChannel *aChannel,
  1611                                          char       **aCookie)
  1613   return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
  1616 nsresult
  1617 nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
  1618                                        nsIChannel *aChannel,
  1619                                        bool aHttpBound,
  1620                                        char** aCookie)
  1622   NS_ENSURE_ARG(aHostURI);
  1623   NS_ENSURE_ARG(aCookie);
  1625   // Determine whether the request is foreign. Failure is acceptable.
  1626   bool isForeign = true;
  1627   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1629   // Get app info, if channel is present.  Else assume default namespace.
  1630   uint32_t appId = NECKO_NO_APP_ID;
  1631   bool inBrowserElement = false;
  1632   if (aChannel) {
  1633     NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1636   bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1638   nsCOMPtr<nsIURI> firstPartyURI;
  1639   mThirdPartyUtil->GetFirstPartyURI(aChannel, nullptr, getter_AddRefs(firstPartyURI));
  1640   bool requireHostMatch;
  1641   nsAutoCString origDomain;
  1642   nsresult rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
  1643   if (NS_FAILED(rv)) {
  1644     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
  1645                       "couldn't get base domain from URI");
  1648   nsAutoCString result;
  1649   GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
  1650                           inBrowserElement, isPrivate, origDomain, result);
  1651   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
  1652   return NS_OK;
  1655 NS_IMETHODIMP
  1656 nsCookieService::SetCookieString(nsIURI     *aHostURI,
  1657                                  nsIPrompt  *aPrompt,
  1658                                  const char *aCookieHeader,
  1659                                  nsIChannel *aChannel)
  1661   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1662   // code that uses this argument by warning if the value is non-null.
  1663   MOZ_ASSERT(!aPrompt);
  1664   if (aPrompt) {
  1665     nsCOMPtr<nsIConsoleService> aConsoleService =
  1666         do_GetService("@mozilla.org/consoleservice;1");
  1667     if (aConsoleService) {
  1668       aConsoleService->LogStringMessage(
  1669         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1672   return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
  1673                                false);
  1676 NS_IMETHODIMP
  1677 nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
  1678                                          nsIURI     *aFirstURI,
  1679                                          nsIPrompt  *aPrompt,
  1680                                          const char *aCookieHeader,
  1681                                          const char *aServerTime,
  1682                                          nsIChannel *aChannel) 
  1684   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1685   // code that uses this argument by warning if the value is non-null.
  1686   MOZ_ASSERT(!aPrompt);
  1687   if (aPrompt) {
  1688     nsCOMPtr<nsIConsoleService> aConsoleService =
  1689         do_GetService("@mozilla.org/consoleservice;1");
  1690     if (aConsoleService) {
  1691       aConsoleService->LogStringMessage(
  1692         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1695   return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
  1696                                true);
  1699 nsresult
  1700 nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
  1701                                        const char *aCookieHeader,
  1702                                        const char *aServerTime,
  1703                                        nsIChannel *aChannel,
  1704                                        bool aFromHttp) 
  1706   NS_ENSURE_ARG(aHostURI);
  1707   NS_ENSURE_ARG(aCookieHeader);
  1709   // Determine whether the request is foreign. Failure is acceptable.
  1710   bool isForeign = true;
  1711   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1713   // Get app info, if channel is present.  Else assume default namespace.
  1714   uint32_t appId = NECKO_NO_APP_ID;
  1715   bool inBrowserElement = false;
  1716   if (aChannel) {
  1717     NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1720   bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1722   nsDependentCString cookieString(aCookieHeader);
  1723   nsDependentCString serverTime(aServerTime ? aServerTime : "");
  1724   SetCookieStringInternal(aHostURI, isForeign, cookieString,
  1725                           serverTime, aFromHttp, appId, inBrowserElement,
  1726                           isPrivate, aChannel);
  1727   return NS_OK;
  1730 // FIXME:MSvB DEBUG DEBUG - DELETEME DELETEME - debug debug - deleteme deleteme
  1731 // FIXME:MSvB Setting a 3rd party cookie (on third.tld) for URL bar browsed
  1732 // FIXME:MSvB   site first.tld causes aHostURI (and later the origin var) to
  1733 // FIXME:MSvB   contain 'third.tld'
  1734 void
  1735 nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
  1736                                          bool                aIsForeign,
  1737                                          nsDependentCString &aCookieHeader,
  1738                                          const nsCString    &aServerTime,
  1739                                          bool                aFromHttp,
  1740                                          uint32_t            aAppId,
  1741                                          bool                aInBrowserElement,
  1742                                          bool                aIsPrivate,
  1743                                          nsIChannel         *aChannel)
  1745   NS_ASSERTION(aHostURI, "null host!");
  1747   if (!mDBState) {
  1748     NS_WARNING("No DBState! Profile already closed?");
  1749     return;
  1752   AutoRestore<DBState*> savePrevDBState(mDBState);
  1753   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  1755   // get the base domain for the host URI.
  1756   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  1757   // file:// URI's (i.e. with an empty host) are allowed, but any other
  1758   // scheme must have a non-empty host. A trailing dot in the host
  1759   // is acceptable.
  1760   bool requireHostMatch;
  1761   nsAutoCString baseDomain;
  1762   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  1763   if (NS_FAILED(rv)) {
  1764     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
  1765                       "couldn't get base domain from URI");
  1766     return;
  1769   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  1771   // check default prefs
  1772   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  1773                                          aCookieHeader.get());
  1774   // fire a notification if third party or if cookie was rejected
  1775   // (but not if there was an error)
  1776   switch (cookieStatus) {
  1777   case STATUS_REJECTED:
  1778     NotifyRejected(aHostURI);
  1779     if (aIsForeign) {
  1780       NotifyThirdParty(aHostURI, false, aChannel);
  1782     return; // Stop here
  1783   case STATUS_REJECTED_WITH_ERROR:
  1784     return;
  1785   case STATUS_ACCEPTED: // Fallthrough
  1786   case STATUS_ACCEPT_SESSION:
  1787     if (aIsForeign) {
  1788       NotifyThirdParty(aHostURI, true, aChannel);
  1790     break;
  1791   default:
  1792     break;
  1795   // parse server local time. this is not just done here for efficiency
  1796   // reasons - if there's an error parsing it, and we need to default it
  1797   // to the current time, we must do it here since the current time in
  1798   // SetCookieInternal() will change for each cookie processed (e.g. if the
  1799   // user is prompted).
  1800   PRTime tempServerTime;
  1801   int64_t serverTime;
  1802   PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
  1803                                        &tempServerTime);
  1804   if (result == PR_SUCCESS) {
  1805     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
  1806   } else {
  1807     serverTime = PR_Now() / PR_USEC_PER_SEC;
  1810   // double keyed cookie boilerplate
  1811   nsCOMPtr<nsIURI> firstPartyURI;
  1812   mThirdPartyUtil->GetFirstPartyURI(aChannel, nullptr, getter_AddRefs(firstPartyURI));
  1813   nsAutoCString origDomain;
  1814   rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
  1815   if (NS_FAILED(rv)) {
  1816     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
  1817                       "couldn't get base domain from URI");
  1820   // process each cookie in the header
  1821   while (SetCookieInternal(aHostURI, key, requireHostMatch, origDomain,
  1822                            cookieStatus, aCookieHeader, serverTime,
  1823                            aFromHttp, aChannel)) {
  1824     // document.cookie can only set one cookie at a time
  1825     if (!aFromHttp)
  1826       break;
  1830 // notify observers that a cookie was rejected due to the users' prefs.
  1831 void
  1832 nsCookieService::NotifyRejected(nsIURI *aHostURI)
  1834   if (mObserverService) {
  1835     mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
  1839 // notify observers that a third-party cookie was accepted/rejected
  1840 // if the cookie issuer is unknown, it defaults to "?"
  1841 void
  1842 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
  1844   if (!mObserverService) {
  1845     return;
  1848   const char* topic;
  1850   if (mDBState != mPrivateDBState) {
  1851     // Regular (non-private) browsing
  1852     if (aIsAccepted) {
  1853       topic = "third-party-cookie-accepted";
  1854     } else {
  1855       topic = "third-party-cookie-rejected";
  1857   } else {
  1858     // Private browsing
  1859     if (aIsAccepted) {
  1860       topic = "private-third-party-cookie-accepted";
  1861     } else {
  1862       topic = "private-third-party-cookie-rejected";
  1866   do {
  1867     // Attempt to find the host of aChannel.
  1868     if (!aChannel) {
  1869       break;
  1871     nsCOMPtr<nsIURI> channelURI;
  1872     nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
  1873     if (NS_FAILED(rv)) {
  1874       break;
  1877     nsAutoCString referringHost;
  1878     rv = channelURI->GetHost(referringHost);
  1879     if (NS_FAILED(rv)) {
  1880       break;
  1883     nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
  1884     mObserverService->NotifyObservers(aHostURI,
  1885                                       topic,
  1886                                       referringHostUTF16.get());
  1887     return;
  1888   } while (0);
  1890   // This can fail for a number of reasons, in which kind we fallback to "?"
  1891   mObserverService->NotifyObservers(aHostURI,
  1892                                     topic,
  1893                                     MOZ_UTF16("?"));
  1896 // notify observers that the cookie list changed. there are five possible
  1897 // values for aData:
  1898 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
  1899 // "added"   means a cookie was added. aSubject is the added cookie.
  1900 // "changed" means a cookie was altered. aSubject is the new cookie.
  1901 // "cleared" means the entire cookie list was cleared. aSubject is null.
  1902 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
  1903 // cookies.
  1904 void
  1905 nsCookieService::NotifyChanged(nsISupports     *aSubject,
  1906                                const char16_t *aData)
  1908   const char* topic = mDBState == mPrivateDBState ?
  1909       "private-cookie-changed" : "cookie-changed";
  1910   if (mObserverService)
  1911     mObserverService->NotifyObservers(aSubject, topic, aData);
  1914 already_AddRefed<nsIArray>
  1915 nsCookieService::CreatePurgeList(nsICookie2* aCookie)
  1917   nsCOMPtr<nsIMutableArray> removedList =
  1918     do_CreateInstance(NS_ARRAY_CONTRACTID);
  1919   removedList->AppendElement(aCookie, false);
  1920   return removedList.forget();
  1923 /******************************************************************************
  1924  * nsCookieService:
  1925  * pref observer impl
  1926  ******************************************************************************/
  1928 void
  1929 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
  1931   int32_t val;
  1932   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
  1933     mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
  1935   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
  1936     mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
  1938   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
  1939     mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
  1941   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
  1942     mCookiePurgeAge =
  1943       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
  1946   bool boolval;
  1947   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
  1948     mThirdPartySession = boolval;
  1951 /******************************************************************************
  1952  * nsICookieManager impl:
  1953  * nsICookieManager
  1954  ******************************************************************************/
  1956 NS_IMETHODIMP
  1957 nsCookieService::RemoveAll()
  1959   if (!mDBState) {
  1960     NS_WARNING("No DBState! Profile already closed?");
  1961     return NS_ERROR_NOT_AVAILABLE;
  1964   RemoveAllFromMemory();
  1966   // clear the cookie file
  1967   if (mDBState->dbConn) {
  1968     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
  1970     // Cancel any pending read. No further results will be received by our
  1971     // read listener.
  1972     if (mDefaultDBState->pendingRead) {
  1973       CancelAsyncRead(true);
  1976     nsCOMPtr<mozIStorageAsyncStatement> stmt;
  1977     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1978       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
  1979     if (NS_SUCCEEDED(rv)) {
  1980       nsCOMPtr<mozIStoragePendingStatement> handle;
  1981       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
  1982         getter_AddRefs(handle));
  1983       NS_ASSERT_SUCCESS(rv);
  1984     } else {
  1985       // Recreate the database.
  1986       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1987         ("RemoveAll(): corruption detected with rv 0x%x", rv));
  1988       HandleCorruptDB(mDefaultDBState);
  1992   NotifyChanged(nullptr, MOZ_UTF16("cleared"));
  1993   return NS_OK;
  1996 static PLDHashOperator
  1997 COMArrayCallback(nsCookieEntry *aEntry,
  1998                  void          *aArg)
  2000   nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
  2002   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  2003   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  2004     data->AppendObject(cookies[i]);
  2007   return PL_DHASH_NEXT;
  2010 NS_IMETHODIMP
  2011 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
  2013   if (!mDBState) {
  2014     NS_WARNING("No DBState! Profile already closed?");
  2015     return NS_ERROR_NOT_AVAILABLE;
  2018   EnsureReadComplete();
  2020   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
  2021   mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
  2023   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  2026 NS_IMETHODIMP
  2027 nsCookieService::Add(const nsACString &aHost,
  2028                      const nsACString &aPath,
  2029                      const nsACString &aName,
  2030                      const nsACString &aValue,
  2031                      bool              aIsSecure,
  2032                      bool              aIsHttpOnly,
  2033                      bool              aIsSession,
  2034                      int64_t           aExpiry)
  2036   if (!mDBState) {
  2037     NS_WARNING("No DBState! Profile already closed?");
  2038     return NS_ERROR_NOT_AVAILABLE;
  2041   // first, normalize the hostname, and fail if it contains illegal characters.
  2042   nsAutoCString host(aHost);
  2043   nsresult rv = NormalizeHost(host);
  2044   NS_ENSURE_SUCCESS(rv, rv);
  2046   // get the base domain for the host URI.
  2047   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  2048   nsAutoCString baseDomain;
  2049   rv = GetBaseDomainFromHost(host, baseDomain);
  2050   NS_ENSURE_SUCCESS(rv, rv);
  2052   int64_t currentTimeInUsec = PR_Now();
  2054   nsRefPtr<nsCookie> cookie =
  2055     nsCookie::Create(aName, aValue, host, baseDomain, aPath,
  2056                      aExpiry,
  2057                      currentTimeInUsec,
  2058                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2059                      aIsSession,
  2060                      aIsSecure,
  2061                      aIsHttpOnly);
  2062   if (!cookie) {
  2063     return NS_ERROR_OUT_OF_MEMORY;
  2066   AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
  2067   return NS_OK;
  2071 nsresult
  2072 nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId,
  2073                         bool aInBrowserElement, const nsACString& aName,
  2074                         const nsACString& aPath, bool aBlocked)
  2076   if (!mDBState) {
  2077     NS_WARNING("No DBState! Profile already closed?");
  2078     return NS_ERROR_NOT_AVAILABLE;
  2081   // first, normalize the hostname, and fail if it contains illegal characters.
  2082   nsAutoCString host(aHost);
  2083   nsresult rv = NormalizeHost(host);
  2084   NS_ENSURE_SUCCESS(rv, rv);
  2086   nsAutoCString baseDomain;
  2087   rv = GetBaseDomainFromHost(host, baseDomain);
  2088   NS_ENSURE_SUCCESS(rv, rv);
  2090   nsListIter matchIter;
  2091   nsRefPtr<nsCookie> cookie;
  2092   if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement),
  2093                  baseDomain,
  2094                  host,
  2095                  PromiseFlatCString(aName),
  2096                  PromiseFlatCString(aPath),
  2097                  matchIter)) {
  2098     cookie = matchIter.Cookie();
  2099     RemoveCookieFromList(matchIter);
  2102   // check if we need to add the host to the permissions blacklist.
  2103   if (aBlocked && mPermissionService) {
  2104     // strip off the domain dot, if necessary
  2105     if (!host.IsEmpty() && host.First() == '.')
  2106       host.Cut(0, 1);
  2108     host.Insert(NS_LITERAL_CSTRING("http://"), 0);
  2110     nsCOMPtr<nsIURI> uri;
  2111     NS_NewURI(getter_AddRefs(uri), host);
  2113     if (uri)
  2114       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
  2117   if (cookie) {
  2118     // Everything's done. Notify observers.
  2119     NotifyChanged(cookie, MOZ_UTF16("deleted"));
  2122   return NS_OK;
  2125 NS_IMETHODIMP
  2126 nsCookieService::Remove(const nsACString &aHost,
  2127                         const nsACString &aName,
  2128                         const nsACString &aPath,
  2129                         bool             aBlocked)
  2131   return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked);
  2134 /******************************************************************************
  2135  * nsCookieService impl:
  2136  * private file I/O functions
  2137  ******************************************************************************/
  2139 // Begin an asynchronous read from the database.
  2140 OpenDBResult
  2141 nsCookieService::Read()
  2143   // Set up a statement for the read. Note that our query specifies that
  2144   // 'baseDomain' not be nullptr -- see below for why.
  2145   nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
  2146   nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2147     "SELECT "
  2148       "name, "
  2149       "value, "
  2150       "host, "
  2151       "path, "
  2152       "expiry, "
  2153       "lastAccessed, "
  2154       "creationTime, "
  2155       "isSecure, "
  2156       "isHttpOnly, "
  2157       "baseDomain, "
  2158       "appId,  "
  2159       "inBrowserElement "
  2160     "FROM moz_cookies "
  2161     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
  2162   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2164   // Set up a statement to delete any rows with a nullptr 'baseDomain'
  2165   // column. This takes care of any cookies set by browsers that don't
  2166   // understand the 'baseDomain' column, where the database schema version
  2167   // is from one that does. (This would occur when downgrading.)
  2168   nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
  2169   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2170     "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
  2171     getter_AddRefs(stmtDeleteNull));
  2172   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2174   // Start a new connection for sync reads, to reduce contention with the
  2175   // background thread. We need to do this before we kick off write statements,
  2176   // since they can lock the database and prevent connections from being opened.
  2177   rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
  2178     getter_AddRefs(mDefaultDBState->syncConn));
  2179   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2181   // Init our readSet hash and execute the statements. Note that, after this
  2182   // point, we cannot fail without altering the cleanup code in InitDBStates()
  2183   // to handle closing of the now-asynchronous connection.
  2184   mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
  2186   mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
  2187   rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
  2188     getter_AddRefs(mDefaultDBState->pendingRead));
  2189   NS_ASSERT_SUCCESS(rv);
  2191   nsCOMPtr<mozIStoragePendingStatement> handle;
  2192   rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
  2193     getter_AddRefs(handle));
  2194   NS_ASSERT_SUCCESS(rv);
  2196   return RESULT_OK;
  2199 // Extract data from a single result row and create an nsCookie.
  2200 // This is templated since 'T' is different for sync vs async results.
  2201 template<class T> nsCookie*
  2202 nsCookieService::GetCookieFromRow(T &aRow)
  2204   // Skip reading 'baseDomain' -- up to the caller.
  2205   nsCString name, value, host, path;
  2206   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
  2207   NS_ASSERT_SUCCESS(rv);
  2208   rv = aRow->GetUTF8String(IDX_VALUE, value);
  2209   NS_ASSERT_SUCCESS(rv);
  2210   rv = aRow->GetUTF8String(IDX_HOST, host);
  2211   NS_ASSERT_SUCCESS(rv);
  2212   rv = aRow->GetUTF8String(IDX_PATH, path);
  2213   NS_ASSERT_SUCCESS(rv);
  2215   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
  2216   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
  2217   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
  2218   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
  2219   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
  2221   nsAutoCString baseDomain;
  2222   rv = GetBaseDomainFromHost(host, baseDomain);
  2223   NS_ASSERT_SUCCESS(rv);
  2225   // Create a new nsCookie and assign the data.
  2226   return nsCookie::Create(name, value, host, baseDomain, path,
  2227                           expiry,
  2228                           lastAccessed,
  2229                           creationTime,
  2230                           false,
  2231                           isSecure,
  2232                           isHttpOnly);
  2235 void
  2236 nsCookieService::AsyncReadComplete()
  2238   // We may be in the private browsing DB state, with a pending read on the
  2239   // default DB state. (This would occur if we started up in private browsing
  2240   // mode.) As long as we do all our operations on the default state, we're OK.
  2241   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2242   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2243   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2245   // Merge the data read on the background thread with the data synchronously
  2246   // read on the main thread. Note that transactions on the cookie table may
  2247   // have occurred on the main thread since, making the background data stale.
  2248   for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
  2249     const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
  2251     // Tiebreak: if the given base domain has already been read in, ignore
  2252     // the background data. Note that readSet may contain domains that were
  2253     // queried but found not to be in the db -- that's harmless.
  2254     if (mDefaultDBState->readSet.GetEntry(tuple.key))
  2255       continue;
  2257     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
  2260   mDefaultDBState->stmtReadDomain = nullptr;
  2261   mDefaultDBState->pendingRead = nullptr;
  2262   mDefaultDBState->readListener = nullptr;
  2263   mDefaultDBState->syncConn = nullptr;
  2264   mDefaultDBState->hostArray.Clear();
  2265   mDefaultDBState->readSet.Clear();
  2267   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
  2268                                   mDefaultDBState->cookieCount));
  2270   mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
  2273 void
  2274 nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
  2276   // We may be in the private browsing DB state, with a pending read on the
  2277   // default DB state. (This would occur if we started up in private browsing
  2278   // mode.) As long as we do all our operations on the default state, we're OK.
  2279   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2280   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2281   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2283   // Cancel the pending read, kill the read listener, and empty the array
  2284   // of data already read in on the background thread.
  2285   mDefaultDBState->readListener->Cancel();
  2286   DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
  2287   NS_ASSERT_SUCCESS(rv);
  2289   mDefaultDBState->stmtReadDomain = nullptr;
  2290   mDefaultDBState->pendingRead = nullptr;
  2291   mDefaultDBState->readListener = nullptr;
  2292   mDefaultDBState->hostArray.Clear();
  2294   // Only clear the 'readSet' table if we no longer need to know what set of
  2295   // data is already accounted for.
  2296   if (aPurgeReadSet)
  2297     mDefaultDBState->readSet.Clear();
  2300 void
  2301 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
  2303   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2304     "not in default db state");
  2306   // Fast path 1: nothing to read, or we've already finished reading.
  2307   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2308     return;
  2310   // Fast path 2: already read in this particular domain.
  2311   if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
  2312     return;
  2314   // Read in the data synchronously.
  2315   // see IDX_NAME, etc. for parameter indexes
  2316   nsresult rv;
  2317   if (!mDefaultDBState->stmtReadDomain) {
  2318     // Cache the statement, since it's likely to be used again.
  2319     rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2320       "SELECT "
  2321         "name, "
  2322         "value, "
  2323         "host, "
  2324         "path, "
  2325         "expiry, "
  2326         "lastAccessed, "
  2327         "creationTime, "
  2328         "isSecure, "
  2329         "isHttpOnly "
  2330       "FROM moz_cookies "
  2331       "WHERE baseDomain = :baseDomain "
  2332       "  AND appId = :appId "
  2333       "  AND inBrowserElement = :inBrowserElement"),
  2334       getter_AddRefs(mDefaultDBState->stmtReadDomain));
  2336     if (NS_FAILED(rv)) {
  2337       // Recreate the database.
  2338       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2339         ("EnsureReadDomain(): corruption detected when creating statement "
  2340          "with rv 0x%x", rv));
  2341       HandleCorruptDB(mDefaultDBState);
  2342       return;
  2346   NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
  2348   mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
  2350   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
  2351     NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
  2352   NS_ASSERT_SUCCESS(rv);
  2353   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2354     NS_LITERAL_CSTRING("appId"), aKey.mAppId);
  2355   NS_ASSERT_SUCCESS(rv);
  2356   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2357     NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
  2358   NS_ASSERT_SUCCESS(rv);
  2361   bool hasResult;
  2362   nsCString name, value, host, path;
  2363   nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
  2364   while (1) {
  2365     rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
  2366     if (NS_FAILED(rv)) {
  2367       // Recreate the database.
  2368       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2369         ("EnsureReadDomain(): corruption detected when reading result "
  2370          "with rv 0x%x", rv));
  2371       HandleCorruptDB(mDefaultDBState);
  2372       return;
  2375     if (!hasResult)
  2376       break;
  2378     array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
  2381   // Add the cookies to the table in a single operation. This makes sure that
  2382   // either all the cookies get added, or in the case of corruption, none.
  2383   for (uint32_t i = 0; i < array.Length(); ++i) {
  2384     AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
  2387   // Add it to the hashset of read entries, so we don't read it again.
  2388   mDefaultDBState->readSet.PutEntry(aKey);
  2390   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2391     ("EnsureReadDomain(): %ld cookies read for base domain %s, "
  2392      " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
  2393      (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
  2396 void
  2397 nsCookieService::EnsureReadComplete()
  2399   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2400     "not in default db state");
  2402   // Fast path 1: nothing to read, or we've already finished reading.
  2403   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2404     return;
  2406   // Cancel the pending read, so we don't get any more results.
  2407   CancelAsyncRead(false);
  2409   // Read in the data synchronously.
  2410   // see IDX_NAME, etc. for parameter indexes
  2411   nsCOMPtr<mozIStorageStatement> stmt;
  2412   nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2413     "SELECT "
  2414       "name, "
  2415       "value, "
  2416       "host, "
  2417       "path, "
  2418       "expiry, "
  2419       "lastAccessed, "
  2420       "creationTime, "
  2421       "isSecure, "
  2422       "isHttpOnly, "
  2423       "baseDomain, "
  2424       "appId,  "
  2425       "inBrowserElement "
  2426     "FROM moz_cookies "
  2427     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
  2429   if (NS_FAILED(rv)) {
  2430     // Recreate the database.
  2431     COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2432       ("EnsureReadComplete(): corruption detected when creating statement "
  2433        "with rv 0x%x", rv));
  2434     HandleCorruptDB(mDefaultDBState);
  2435     return;
  2438   nsCString baseDomain, name, value, host, path;
  2439   uint32_t appId;
  2440   bool inBrowserElement, hasResult;
  2441   nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
  2442   while (1) {
  2443     rv = stmt->ExecuteStep(&hasResult);
  2444     if (NS_FAILED(rv)) {
  2445       // Recreate the database.
  2446       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2447         ("EnsureReadComplete(): corruption detected when reading result "
  2448          "with rv 0x%x", rv));
  2449       HandleCorruptDB(mDefaultDBState);
  2450       return;
  2453     if (!hasResult)
  2454       break;
  2456     // Make sure we haven't already read the data.
  2457     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
  2458     appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID));
  2459     inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM));
  2460     nsCookieKey key(baseDomain, appId, inBrowserElement);
  2461     if (mDefaultDBState->readSet.GetEntry(key))
  2462       continue;
  2464     CookieDomainTuple* tuple = array.AppendElement();
  2465     tuple->key = key;
  2466     tuple->cookie = GetCookieFromRow(stmt);
  2469   // Add the cookies to the table in a single operation. This makes sure that
  2470   // either all the cookies get added, or in the case of corruption, none.
  2471   for (uint32_t i = 0; i < array.Length(); ++i) {
  2472     CookieDomainTuple& tuple = array[i];
  2473     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
  2474       false);
  2477   mDefaultDBState->syncConn = nullptr;
  2478   mDefaultDBState->readSet.Clear();
  2480   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2481     ("EnsureReadComplete(): %ld cookies read", array.Length()));
  2484 NS_IMETHODIMP
  2485 nsCookieService::ImportCookies(nsIFile *aCookieFile)
  2487   if (!mDBState) {
  2488     NS_WARNING("No DBState! Profile already closed?");
  2489     return NS_ERROR_NOT_AVAILABLE;
  2492   // Make sure we're in the default DB state. We don't want people importing
  2493   // cookies into a private browsing session!
  2494   if (mDBState != mDefaultDBState) {
  2495     NS_WARNING("Trying to import cookies in a private browsing session!");
  2496     return NS_ERROR_NOT_AVAILABLE;
  2499   nsresult rv;
  2500   nsCOMPtr<nsIInputStream> fileInputStream;
  2501   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
  2502   if (NS_FAILED(rv)) return rv;
  2504   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
  2505   if (NS_FAILED(rv)) return rv;
  2507   // First, ensure we've read in everything from the database, if we have one.
  2508   EnsureReadComplete();
  2510   static const char kTrue[] = "TRUE";
  2512   nsAutoCString buffer, baseDomain;
  2513   bool isMore = true;
  2514   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
  2515   nsASingleFragmentCString::char_iterator iter;
  2516   int32_t numInts;
  2517   int64_t expires;
  2518   bool isDomain, isHttpOnly = false;
  2519   uint32_t originalCookieCount = mDefaultDBState->cookieCount;
  2521   int64_t currentTimeInUsec = PR_Now();
  2522   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2523   // we use lastAccessedCounter to keep cookies in recently-used order,
  2524   // so we start by initializing to currentTime (somewhat arbitrary)
  2525   int64_t lastAccessedCounter = currentTimeInUsec;
  2527   /* file format is:
  2529    * host \t isDomain \t path \t secure \t expires \t name \t cookie
  2531    * if this format isn't respected we move onto the next line in the file.
  2532    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
  2533    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
  2534    * expires is a int64_t integer
  2535    * note 1: cookie can contain tabs.
  2536    * note 2: cookies will be stored in order of lastAccessed time:
  2537    *         most-recently used come first; least-recently-used come last.
  2538    */
  2540   /*
  2541    * ...but due to bug 178933, we hide HttpOnly cookies from older code
  2542    * in a comment, so they don't expose HttpOnly cookies to JS.
  2544    * The format for HttpOnly cookies is
  2546    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
  2548    */
  2550   // We will likely be adding a bunch of cookies to the DB, so we use async
  2551   // batching with storage to make this super fast.
  2552   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2553   if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
  2554     mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2557   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
  2558     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
  2559       isHttpOnly = true;
  2560       hostIndex = sizeof(kHttpOnlyPrefix) - 1;
  2561     } else if (buffer.IsEmpty() || buffer.First() == '#') {
  2562       continue;
  2563     } else {
  2564       isHttpOnly = false;
  2565       hostIndex = 0;
  2568     // this is a cheap, cheesy way of parsing a tab-delimited line into
  2569     // string indexes, which can be lopped off into substrings. just for
  2570     // purposes of obfuscation, it also checks that each token was found.
  2571     // todo: use iterators?
  2572     if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
  2573         (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
  2574         (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
  2575         (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
  2576         (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
  2577         (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
  2578       continue;
  2581     // check the expirytime first - if it's expired, ignore
  2582     // nullstomp the trailing tab, to avoid copying the string
  2583     buffer.BeginWriting(iter);
  2584     *(iter += nameIndex - 1) = char(0);
  2585     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
  2586     if (numInts != 1 || expires < currentTime) {
  2587       continue;
  2590     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
  2591     const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
  2592     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
  2593     // and discard
  2594     if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
  2595         host.FindChar(':') != kNotFound) {
  2596       continue;
  2599     // compute the baseDomain from the host
  2600     rv = GetBaseDomainFromHost(host, baseDomain);
  2601     if (NS_FAILED(rv))
  2602       continue;
  2604     // pre-existing cookies have appId=0, inBrowser=false
  2605     nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  2607     // Create a new nsCookie and assign the data. We don't know the cookie
  2608     // creation time, so just use the current time to generate a unique one.
  2609     nsRefPtr<nsCookie> newCookie =
  2610       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
  2611                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
  2612                        host,
  2613                        baseDomain,
  2614                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
  2615                        expires,
  2616                        lastAccessedCounter,
  2617                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2618                        false,
  2619                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
  2620                        isHttpOnly);
  2621     if (!newCookie) {
  2622       return NS_ERROR_OUT_OF_MEMORY;
  2625     // trick: preserve the most-recently-used cookie ordering,
  2626     // by successively decrementing the lastAccessed time
  2627     lastAccessedCounter--;
  2629     if (originalCookieCount == 0) {
  2630       AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
  2632     else {
  2633       AddInternal(key, newCookie, currentTimeInUsec,
  2634                   nullptr, nullptr, true);
  2638   // If we need to write to disk, do so now.
  2639   if (paramsArray) {
  2640     uint32_t length;
  2641     paramsArray->GetLength(&length);
  2642     if (length) {
  2643       rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
  2644       NS_ASSERT_SUCCESS(rv);
  2645       nsCOMPtr<mozIStoragePendingStatement> handle;
  2646       rv = mDefaultDBState->stmtInsert->ExecuteAsync(
  2647         mDefaultDBState->insertListener, getter_AddRefs(handle));
  2648       NS_ASSERT_SUCCESS(rv);
  2653   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
  2654     mDefaultDBState->cookieCount));
  2656   return NS_OK;
  2659 /******************************************************************************
  2660  * nsCookieService impl:
  2661  * private GetCookie/SetCookie helpers
  2662  ******************************************************************************/
  2664 // helper function for GetCookieList
  2665 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
  2667 // Comparator class for sorting cookies before sending to a server.
  2668 class CompareCookiesForSending
  2670 public:
  2671   bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2673     return aCookie1->CreationTime() == aCookie2->CreationTime() &&
  2674            aCookie2->Path().Length() == aCookie1->Path().Length();
  2677   bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2679     // compare by cookie path length in accordance with RFC2109
  2680     int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
  2681     if (result != 0)
  2682       return result < 0;
  2684     // when path lengths match, older cookies should be listed first.  this is
  2685     // required for backwards compatibility since some websites erroneously
  2686     // depend on receiving cookies in the order in which they were sent to the
  2687     // browser!  see bug 236772.
  2688     return aCookie1->CreationTime() < aCookie2->CreationTime();
  2690 };
  2692 void
  2693 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
  2694                                          bool aIsForeign,
  2695                                          bool aHttpBound,
  2696                                          uint32_t aAppId,
  2697                                          bool aInBrowserElement,
  2698                                          bool aIsPrivate,
  2699                                          nsCString &aOrigDomain,
  2700                                          nsCString &aCookieString)
  2702   NS_ASSERTION(aHostURI, "null host!");
  2704   if (!mDBState) {
  2705     NS_WARNING("No DBState! Profile already closed?");
  2706     return;
  2709   AutoRestore<DBState*> savePrevDBState(mDBState);
  2710   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  2712   // get the base domain, host, and path from the URI.
  2713   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
  2714   // file:// URI's (i.e. with an empty host) are allowed, but any other
  2715   // scheme must have a non-empty host. A trailing dot in the host
  2716   // is acceptable.
  2717   bool requireHostMatch;
  2718   nsAutoCString baseDomain, hostFromURI, pathFromURI;
  2719   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  2720   if (NS_SUCCEEDED(rv))
  2721     rv = aHostURI->GetAsciiHost(hostFromURI);
  2722   if (NS_SUCCEEDED(rv))
  2723     rv = aHostURI->GetPath(pathFromURI);
  2724   if (NS_FAILED(rv)) {
  2725     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
  2726     return;
  2729   // check default prefs
  2730   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  2731                                          nullptr);
  2732   // for GetCookie(), we don't fire rejection notifications.
  2733   switch (cookieStatus) {
  2734   case STATUS_REJECTED:
  2735   case STATUS_REJECTED_WITH_ERROR:
  2736     return;
  2737   default:
  2738     break;
  2741   // check if aHostURI is using an https secure protocol.
  2742   // if it isn't, then we can't send a secure cookie over the connection.
  2743   // if SchemeIs fails, assume an insecure connection, to be on the safe side
  2744   bool isSecure;
  2745   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
  2746     isSecure = false;
  2749   nsCookie *cookie;
  2750   nsAutoTArray<nsCookie*, 8> foundCookieList;
  2751   int64_t currentTimeInUsec = PR_Now();
  2752   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2753   bool stale = false;
  2755   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  2756   EnsureReadDomain(key);
  2758   // perform the hash lookup
  2759   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  2760   if (!entry)
  2761     return;
  2763   // iterate the cookies!
  2764   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  2765   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  2766     cookie = cookies[i];
  2768     // Check the origin key. We only continue if the
  2769     // saved origin matches matches the origin domain.
  2770     // FIXME:MSvB, other places iterate cookies too, handle them likewise?
  2771     if (cookie->Origin() != aOrigDomain) {
  2772       continue;
  2775     // check the host, since the base domain lookup is conservative.
  2776     // first, check for an exact host or domain cookie match, e.g. "google.com"
  2777     // or ".google.com"; second a subdomain match, e.g.
  2778     // host = "mail.google.com", cookie domain = ".google.com".
  2779     if (cookie->RawHost() != hostFromURI &&
  2780         !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
  2781       continue;
  2783     // if the cookie is secure and the host scheme isn't, we can't send it
  2784     if (cookie->IsSecure() && !isSecure)
  2785       continue;
  2787     // if the cookie is httpOnly and it's not going directly to the HTTP
  2788     // connection, don't send it
  2789     if (cookie->IsHttpOnly() && !aHttpBound)
  2790       continue;
  2792     // calculate cookie path length, excluding trailing '/'
  2793     uint32_t cookiePathLen = cookie->Path().Length();
  2794     if (cookiePathLen > 0 && cookie->Path().Last() == '/')
  2795       --cookiePathLen;
  2797     // if the nsIURI path is shorter than the cookie path, don't send it back
  2798     if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
  2799       continue;
  2801     if (pathFromURI.Length() > cookiePathLen &&
  2802         !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
  2803       /*
  2804        * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
  2805        * '/' is the "standard" case; the '?' test allows a site at host/abc?def
  2806        * to receive a cookie that has a path attribute of abc.  this seems
  2807        * strange but at least one major site (citibank, bug 156725) depends
  2808        * on it.  The test for # and ; are put in to proactively avoid problems
  2809        * with other sites - these are the only other chars allowed in the path.
  2810        */
  2811       continue;
  2814     // check if the cookie has expired
  2815     if (cookie->Expiry() <= currentTime) {
  2816       continue;
  2819     // all checks passed - add to list and check if lastAccessed stamp needs updating
  2820     foundCookieList.AppendElement(cookie);
  2821     if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2822       stale = true;
  2825   int32_t count = foundCookieList.Length();
  2826   if (count == 0)
  2827     return;
  2829   // update lastAccessed timestamps. we only do this if the timestamp is stale
  2830   // by a certain amount, to avoid thrashing the db during pageload.
  2831   if (stale) {
  2832     // Create an array of parameters to bind to our update statement. Batching
  2833     // is OK here since we're updating cookies with no interleaved operations.
  2834     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2835     mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
  2836     if (mDBState->dbConn) {
  2837       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2840     for (int32_t i = 0; i < count; ++i) {
  2841       cookie = foundCookieList.ElementAt(i);
  2843       if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2844         UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
  2846     // Update the database now if necessary.
  2847     if (paramsArray) {
  2848       uint32_t length;
  2849       paramsArray->GetLength(&length);
  2850       if (length) {
  2851         DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  2852         NS_ASSERT_SUCCESS(rv);
  2853         nsCOMPtr<mozIStoragePendingStatement> handle;
  2854         rv = stmt->ExecuteAsync(mDBState->updateListener,
  2855           getter_AddRefs(handle));
  2856         NS_ASSERT_SUCCESS(rv);
  2861   // return cookies in order of path length; longest to shortest.
  2862   // this is required per RFC2109.  if cookies match in length,
  2863   // then sort by creation time (see bug 236772).
  2864   foundCookieList.Sort(CompareCookiesForSending());
  2866   for (int32_t i = 0; i < count; ++i) {
  2867     cookie = foundCookieList.ElementAt(i);
  2869     // check if we have anything to write
  2870     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
  2871       // if we've already added a cookie to the return list, append a "; " so
  2872       // that subsequent cookies are delimited in the final list.
  2873       if (!aCookieString.IsEmpty()) {
  2874         aCookieString.AppendLiteral("; ");
  2877       if (!cookie->Name().IsEmpty()) {
  2878         // we have a name and value - write both
  2879         aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
  2880       } else {
  2881         // just write value
  2882         aCookieString += cookie->Value();
  2887   if (!aCookieString.IsEmpty())
  2888     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
  2891 // processes a single cookie, and returns true if there are more cookies
  2892 // to be processed
  2893 bool
  2894 nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
  2895                                    const nsCookieKey             &aKey,
  2896                                    bool                           aRequireHostMatch,
  2897                                    const nsCString               &aOrigin,
  2898                                    CookieStatus                   aStatus,
  2899                                    nsDependentCString            &aCookieHeader,
  2900                                    int64_t                        aServerTime,
  2901                                    bool                           aFromHttp,
  2902                                    nsIChannel                    *aChannel)
  2904   NS_ASSERTION(aHostURI, "null host!");
  2906   // create a stack-based nsCookieAttributes, to store all the
  2907   // attributes parsed from the cookie
  2908   nsCookieAttributes cookieAttributes;
  2910   // init expiryTime such that session cookies won't prematurely expire
  2911   cookieAttributes.expiryTime = INT64_MAX;
  2913   // aCookieHeader is an in/out param to point to the next cookie, if
  2914   // there is one. Save the present value for logging purposes
  2915   nsDependentCString savedCookieHeader(aCookieHeader);
  2917   // newCookie says whether there are multiple cookies in the header;
  2918   // so we can handle them separately.
  2919   bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
  2921   int64_t currentTimeInUsec = PR_Now();
  2923   // calculate expiry time of cookie.
  2924   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
  2925                                          currentTimeInUsec / PR_USEC_PER_SEC);
  2926   if (aStatus == STATUS_ACCEPT_SESSION) {
  2927     // force lifetime to session. note that the expiration time, if set above,
  2928     // will still apply.
  2929     cookieAttributes.isSession = true;
  2932   // reject cookie if it's over the size limit, per RFC2109
  2933   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
  2934     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
  2935     return newCookie;
  2938   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
  2939     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
  2940     return newCookie;
  2943   // domain & path checks
  2944   if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
  2945     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
  2946     return newCookie;
  2948   if (!CheckPath(cookieAttributes, aHostURI)) {
  2949     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
  2950     return newCookie;
  2953   // create a new nsCookie and copy attributes
  2954 //FIXME:MSvB, The name and value vars are neither host nor key
  2955 //FIXME:MSvB, host shows up in cookie inspector, as a index key
  2956   nsRefPtr<nsCookie> cookie =
  2957     nsCookie::Create(cookieAttributes.name,
  2958                      cookieAttributes.value,
  2959                      cookieAttributes.host,
  2960                      aOrigin,
  2961                      cookieAttributes.path,
  2962                      cookieAttributes.expiryTime,
  2963                      currentTimeInUsec,
  2964                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2965                      cookieAttributes.isSession,
  2966                      cookieAttributes.isSecure,
  2967                      cookieAttributes.isHttpOnly);
  2968   if (!cookie)
  2969     return newCookie;
  2971   // check permissions from site permission list, or ask the user,
  2972   // to determine if we can set the cookie
  2973   if (mPermissionService) {
  2974     bool permission;
  2975     mPermissionService->CanSetCookie(aHostURI,
  2976                                      aChannel,
  2977                                      static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
  2978                                      &cookieAttributes.isSession,
  2979                                      &cookieAttributes.expiryTime,
  2980                                      &permission);
  2981     if (!permission) {
  2982       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
  2983       NotifyRejected(aHostURI);
  2984       return newCookie;
  2987     // update isSession and expiry attributes, in case they changed
  2988     cookie->SetIsSession(cookieAttributes.isSession);
  2989     cookie->SetExpiry(cookieAttributes.expiryTime);
  2992   // add the cookie to the list. AddInternal() takes care of logging.
  2993   // we get the current time again here, since it may have changed during prompting
  2994   AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
  2995               aFromHttp);
  2996   return newCookie;
  2999 // this is a backend function for adding a cookie to the list, via SetCookie.
  3000 // also used in the cookie manager, for profile migration from IE.
  3001 // it either replaces an existing cookie; or adds the cookie to the hashtable,
  3002 // and deletes a cookie (if maximum number of cookies has been
  3003 // reached). also performs list maintenance by removing expired cookies.
  3004 void
  3005 nsCookieService::AddInternal(const nsCookieKey             &aKey,
  3006                              nsCookie                      *aCookie,
  3007                              int64_t                        aCurrentTimeInUsec,
  3008                              nsIURI                        *aHostURI,
  3009                              const char                    *aCookieHeader,
  3010                              bool                           aFromHttp)
  3012   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
  3014   // if the new cookie is httponly, make sure we're not coming from script
  3015   if (!aFromHttp && aCookie->IsHttpOnly()) {
  3016     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3017       "cookie is httponly; coming from script");
  3018     return;
  3021   nsListIter matchIter;
  3022   bool foundCookie = FindCookie(aKey, aCookie->Origin(),
  3023     aCookie->Host(), aCookie->Name(), aCookie->Path(), matchIter);
  3025   nsRefPtr<nsCookie> oldCookie;
  3026   nsCOMPtr<nsIArray> purgedList;
  3027   if (foundCookie) {
  3028     oldCookie = matchIter.Cookie();
  3030     // Check if the old cookie is stale (i.e. has already expired). If so, we
  3031     // need to be careful about the semantics of removing it and adding the new
  3032     // cookie: we want the behavior wrt adding the new cookie to be the same as
  3033     // if it didn't exist, but we still want to fire a removal notification.
  3034     if (oldCookie->Expiry() <= currentTime) {
  3035       if (aCookie->Expiry() <= currentTime) {
  3036         // The new cookie has expired and the old one is stale. Nothing to do.
  3037         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3038           "cookie has already expired");
  3039         return;
  3042       // Remove the stale cookie. We save notification for later, once all list
  3043       // modifications are complete.
  3044       RemoveCookieFromList(matchIter);
  3045       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3046         "stale cookie was purged");
  3047       purgedList = CreatePurgeList(oldCookie);
  3049       // We've done all we need to wrt removing and notifying the stale cookie.
  3050       // From here on out, we pretend pretend it didn't exist, so that we
  3051       // preserve expected notification semantics when adding the new cookie.
  3052       foundCookie = false;
  3054     } else {
  3055       // If the old cookie is httponly, make sure we're not coming from script.
  3056       if (!aFromHttp && oldCookie->IsHttpOnly()) {
  3057         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3058           "previously stored cookie is httponly; coming from script");
  3059         return;
  3062       // Remove the old cookie.
  3063       RemoveCookieFromList(matchIter);
  3065       // If the new cookie has expired -- i.e. the intent was simply to delete
  3066       // the old cookie -- then we're done.
  3067       if (aCookie->Expiry() <= currentTime) {
  3068         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3069           "previously stored cookie was deleted");
  3070         NotifyChanged(oldCookie, MOZ_UTF16("deleted"));
  3071         return;
  3074       // Preserve creation time of cookie for ordering purposes.
  3075       aCookie->SetCreationTime(oldCookie->CreationTime());
  3078   } else {
  3079     // check if cookie has already expired
  3080     if (aCookie->Expiry() <= currentTime) {
  3081       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3082         "cookie has already expired");
  3083       return;
  3086     // check if we have to delete an old cookie.
  3087     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  3088     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
  3089       nsListIter iter;
  3090       FindStaleCookie(entry, currentTime, iter);
  3091       oldCookie = iter.Cookie();
  3093       // remove the oldest cookie from the domain
  3094       RemoveCookieFromList(iter);
  3095       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
  3096       purgedList = CreatePurgeList(oldCookie);
  3098     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
  3099       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
  3100       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
  3101       if (maxAge >= purgeAge) {
  3102         // we're over both size and age limits by 10%; time to purge the table!
  3103         // do this by:
  3104         // 1) removing expired cookies;
  3105         // 2) evicting the balance of old cookies until we reach the size limit.
  3106         // note that the cookieOldestTime indicator can be pessimistic - if it's
  3107         // older than the actual oldest cookie, we'll just purge more eagerly.
  3108         purgedList = PurgeCookies(aCurrentTimeInUsec);
  3113   // Add the cookie to the db. We do not supply a params array for batching
  3114   // because this might result in removals and additions being out of order.
  3115   AddCookieToList(aKey, aCookie, mDBState, nullptr);
  3116   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
  3118   // Now that list mutations are complete, notify observers. We do it here
  3119   // because observers may themselves attempt to mutate the list.
  3120   if (purgedList) {
  3121     NotifyChanged(purgedList, MOZ_UTF16("batch-deleted"));
  3124   NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
  3125                                      : MOZ_UTF16("added"));
  3128 /******************************************************************************
  3129  * nsCookieService impl:
  3130  * private cookie header parsing functions
  3131  ******************************************************************************/
  3133 // The following comment block elucidates the function of ParseAttributes.
  3134 /******************************************************************************
  3135  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
  3136  ** please note: this BNF deviates from both specifications, and reflects this
  3137  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
  3139  ** Differences from RFC2109/2616 and explanations:
  3140     1. implied *LWS
  3141          The grammar described by this specification is word-based. Except
  3142          where noted otherwise, linear white space (<LWS>) can be included
  3143          between any two adjacent words (token or quoted-string), and
  3144          between adjacent words and separators, without changing the
  3145          interpretation of a field.
  3146        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
  3148     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
  3149        common use inside values.
  3151     3. tokens and values have looser restrictions on allowed characters than
  3152        spec. This is also due to certain characters being in common use inside
  3153        values. We allow only '=' to separate token/value pairs, and ';' to
  3154        terminate tokens or values. <LWS> is allowed within tokens and values
  3155        (see bug 206022).
  3157     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
  3158        reject control chars or non-ASCII chars. This is erring on the loose
  3159        side, since there's probably no good reason to enforce this strictness.
  3161     5. cookie <NAME> is optional, where spec requires it. This is a fairly
  3162        trivial case, but allows the flexibility of setting only a cookie <VALUE>
  3163        with a blank <NAME> and is required by some sites (see bug 169091).
  3165     6. Attribute "HttpOnly", not covered in the RFCs, is supported
  3166        (see bug 178993).
  3168  ** Begin BNF:
  3169     token         = 1*<any allowed-chars except separators>
  3170     value         = 1*<any allowed-chars except value-sep>
  3171     separators    = ";" | "="
  3172     value-sep     = ";"
  3173     cookie-sep    = CR | LF
  3174     allowed-chars = <any OCTET except NUL or cookie-sep>
  3175     OCTET         = <any 8-bit sequence of data>
  3176     LWS           = SP | HT
  3177     NUL           = <US-ASCII NUL, null control character (0)>
  3178     CR            = <US-ASCII CR, carriage return (13)>
  3179     LF            = <US-ASCII LF, linefeed (10)>
  3180     SP            = <US-ASCII SP, space (32)>
  3181     HT            = <US-ASCII HT, horizontal-tab (9)>
  3183     set-cookie    = "Set-Cookie:" cookies
  3184     cookies       = cookie *( cookie-sep cookie )
  3185     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
  3186     NAME          = token                                ; cookie name
  3187     VALUE         = value                                ; cookie value
  3188     cookie-av     = token ["=" value]
  3190     valid values for cookie-av (checked post-parsing) are:
  3191     cookie-av     = "Path"    "=" value
  3192                   | "Domain"  "=" value
  3193                   | "Expires" "=" value
  3194                   | "Max-Age" "=" value
  3195                   | "Comment" "=" value
  3196                   | "Version" "=" value
  3197                   | "Secure"
  3198                   | "HttpOnly"
  3200 ******************************************************************************/
  3202 // helper functions for GetTokenValue
  3203 static inline bool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
  3204 static inline bool isterminator     (char c) { return c == '\n' || c == '\r'; }
  3205 static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
  3206 static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
  3208 // Parse a single token/value pair.
  3209 // Returns true if a cookie terminator is found, so caller can parse new cookie.
  3210 bool
  3211 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
  3212                                nsASingleFragmentCString::const_char_iterator &aEndIter,
  3213                                nsDependentCSubstring                         &aTokenString,
  3214                                nsDependentCSubstring                         &aTokenValue,
  3215                                bool                                          &aEqualsFound)
  3217   nsASingleFragmentCString::const_char_iterator start, lastSpace;
  3218   // initialize value string to clear garbage
  3219   aTokenValue.Rebind(aIter, aIter);
  3221   // find <token>, including any <LWS> between the end-of-token and the
  3222   // token separator. we'll remove trailing <LWS> next
  3223   while (aIter != aEndIter && iswhitespace(*aIter))
  3224     ++aIter;
  3225   start = aIter;
  3226   while (aIter != aEndIter && !istokenseparator(*aIter))
  3227     ++aIter;
  3229   // remove trailing <LWS>; first check we're not at the beginning
  3230   lastSpace = aIter;
  3231   if (lastSpace != start) {
  3232     while (--lastSpace != start && iswhitespace(*lastSpace))
  3233       continue;
  3234     ++lastSpace;
  3236   aTokenString.Rebind(start, lastSpace);
  3238   aEqualsFound = (*aIter == '=');
  3239   if (aEqualsFound) {
  3240     // find <value>
  3241     while (++aIter != aEndIter && iswhitespace(*aIter))
  3242       continue;
  3244     start = aIter;
  3246     // process <token>
  3247     // just look for ';' to terminate ('=' allowed)
  3248     while (aIter != aEndIter && !isvalueseparator(*aIter))
  3249       ++aIter;
  3251     // remove trailing <LWS>; first check we're not at the beginning
  3252     if (aIter != start) {
  3253       lastSpace = aIter;
  3254       while (--lastSpace != start && iswhitespace(*lastSpace))
  3255         continue;
  3256       aTokenValue.Rebind(start, ++lastSpace);
  3260   // aIter is on ';', or terminator, or EOS
  3261   if (aIter != aEndIter) {
  3262     // if on terminator, increment past & return true to process new cookie
  3263     if (isterminator(*aIter)) {
  3264       ++aIter;
  3265       return true;
  3267     // fall-through: aIter is on ';', increment and return false
  3268     ++aIter;
  3270   return false;
  3273 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
  3274 // cookie struct here, because we don't know which one to use until we've parsed the header.
  3275 bool
  3276 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
  3277                                  nsCookieAttributes &aCookieAttributes)
  3279   static const char kPath[]    = "path";
  3280   static const char kDomain[]  = "domain";
  3281   static const char kExpires[] = "expires";
  3282   static const char kMaxage[]  = "max-age";
  3283   static const char kSecure[]  = "secure";
  3284   static const char kHttpOnly[]  = "httponly";
  3286   nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
  3287   nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
  3288   aCookieHeader.BeginReading(cookieStart);
  3289   aCookieHeader.EndReading(cookieEnd);
  3291   aCookieAttributes.isSecure = false;
  3292   aCookieAttributes.isHttpOnly = false;
  3294   nsDependentCSubstring tokenString(cookieStart, cookieStart);
  3295   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
  3296   bool newCookie, equalsFound;
  3298   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
  3299   // if we find multiple cookies, return for processing
  3300   // note: if there's no '=', we assume token is <VALUE>. this is required by
  3301   //       some sites (see bug 169091).
  3302   // XXX fix the parser to parse according to <VALUE> grammar for this case
  3303   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3304   if (equalsFound) {
  3305     aCookieAttributes.name = tokenString;
  3306     aCookieAttributes.value = tokenValue;
  3307   } else {
  3308     aCookieAttributes.value = tokenString;
  3311   // extract remaining attributes
  3312   while (cookieStart != cookieEnd && !newCookie) {
  3313     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3315     if (!tokenValue.IsEmpty()) {
  3316       tokenValue.BeginReading(tempBegin);
  3317       tokenValue.EndReading(tempEnd);
  3320     // decide which attribute we have, and copy the string
  3321     if (tokenString.LowerCaseEqualsLiteral(kPath))
  3322       aCookieAttributes.path = tokenValue;
  3324     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
  3325       aCookieAttributes.host = tokenValue;
  3327     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
  3328       aCookieAttributes.expires = tokenValue;
  3330     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
  3331       aCookieAttributes.maxage = tokenValue;
  3333     // ignore any tokenValue for isSecure; just set the boolean
  3334     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
  3335       aCookieAttributes.isSecure = true;
  3337     // ignore any tokenValue for isHttpOnly (see bug 178993);
  3338     // just set the boolean
  3339     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
  3340       aCookieAttributes.isHttpOnly = true;
  3343   // rebind aCookieHeader, in case we need to process another cookie
  3344   aCookieHeader.Rebind(cookieStart, cookieEnd);
  3345   return newCookie;
  3348 /******************************************************************************
  3349  * nsCookieService impl:
  3350  * private domain & permission compliance enforcement functions
  3351  ******************************************************************************/
  3353 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
  3354 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
  3355 // dot may be present. If aHostURI is an IP address, an alias such as
  3356 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
  3357 // be the exact host, and aRequireHostMatch will be true to indicate that
  3358 // substring matches should not be performed.
  3359 nsresult
  3360 nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
  3361                                nsCString &aBaseDomain,
  3362                                bool      &aRequireHostMatch)
  3364   // get the base domain. this will fail if the host contains a leading dot,
  3365   // more than one trailing dot, or is otherwise malformed.
  3366   nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
  3367   aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3368                       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
  3369   if (aRequireHostMatch) {
  3370     // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
  3371     // such as 'co.uk', or the empty string. use the host as a key in such
  3372     // cases.
  3373     rv = aHostURI->GetAsciiHost(aBaseDomain);
  3375   NS_ENSURE_SUCCESS(rv, rv);
  3377   // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
  3378   if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
  3379     return NS_ERROR_INVALID_ARG;
  3381   // block any URIs without a host that aren't file:// URIs.
  3382   if (aBaseDomain.IsEmpty()) {
  3383     bool isFileURI = false;
  3384     aHostURI->SchemeIs("file", &isFileURI);
  3385     if (!isFileURI)
  3386       return NS_ERROR_INVALID_ARG;
  3389   return NS_OK;
  3392 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
  3393 // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
  3394 // that aHost is already normalized, and it may contain a leading dot
  3395 // (indicating that it represents a domain). A trailing dot may be present.
  3396 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
  3397 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
  3398 // leading dot will be treated as an error.
  3399 nsresult
  3400 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
  3401                                        nsCString        &aBaseDomain)
  3403   // aHost must not be the string '.'.
  3404   if (aHost.Length() == 1 && aHost.Last() == '.')
  3405     return NS_ERROR_INVALID_ARG;
  3407   // aHost may contain a leading dot; if so, strip it now.
  3408   bool domain = !aHost.IsEmpty() && aHost.First() == '.';
  3410   // get the base domain. this will fail if the host contains a leading dot,
  3411   // more than one trailing dot, or is otherwise malformed.
  3412   nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
  3413   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3414       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
  3415     // aHost is either an IP address, an alias such as 'localhost', an eTLD
  3416     // such as 'co.uk', or the empty string. use the host as a key in such
  3417     // cases; however, we reject any such hosts with a leading dot, since it
  3418     // doesn't make sense for them to be domain cookies.
  3419     if (domain)
  3420       return NS_ERROR_INVALID_ARG;
  3422     aBaseDomain = aHost;
  3423     return NS_OK;
  3425   return rv;
  3428 // Normalizes the given hostname, component by component. ASCII/ACE
  3429 // components are lower-cased, and UTF-8 components are normalized per
  3430 // RFC 3454 and converted to ACE.
  3431 nsresult
  3432 nsCookieService::NormalizeHost(nsCString &aHost)
  3434   if (!IsASCII(aHost)) {
  3435     nsAutoCString host;
  3436     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
  3437     if (NS_FAILED(rv))
  3438       return rv;
  3440     aHost = host;
  3443   ToLowerCase(aHost);
  3444   return NS_OK;
  3447 // returns true if 'a' is equal to or a subdomain of 'b',
  3448 // assuming no leading dots are present.
  3449 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
  3451   if (a == b)
  3452     return true;
  3453   if (a.Length() > b.Length())
  3454     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
  3455   return false;
  3458 CookieStatus
  3459 nsCookieService::CheckPrefs(nsIURI          *aHostURI,
  3460                             bool             aIsForeign,
  3461                             bool             aRequireHostMatch,
  3462                             const char      *aCookieHeader)
  3464   nsresult rv;
  3466   // don't let ftp sites get/set cookies (could be a security issue)
  3467   bool ftp;
  3468   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
  3469     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
  3470     return STATUS_REJECTED_WITH_ERROR;
  3473   // check the permission list first; if we find an entry, it overrides
  3474   // default prefs. see bug 184059.
  3475   if (mPermissionService) {
  3476     nsCookieAccess access;
  3477     // Not passing an nsIChannel here is probably OK; our implementation
  3478     // doesn't do anything with it anyway.
  3479     rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
  3481     // if we found an entry, use it
  3482     if (NS_SUCCEEDED(rv)) {
  3483       switch (access) {
  3484       case nsICookiePermission::ACCESS_DENY:
  3485         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3486                           aCookieHeader, "cookies are blocked for this site");
  3487         return STATUS_REJECTED;
  3489       case nsICookiePermission::ACCESS_ALLOW:
  3490         return STATUS_ACCEPTED;
  3492       case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
  3493         if (aIsForeign) {
  3494           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3495                             aCookieHeader, "third party cookies are blocked "
  3496                             "for this site");
  3497           return STATUS_REJECTED;
  3500         return STATUS_ACCEPTED;
  3502       case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
  3503         if (!aIsForeign)
  3504           return STATUS_ACCEPTED;
  3505         uint32_t priorCookieCount = 0;
  3506         nsAutoCString hostFromURI;
  3507         aHostURI->GetHost(hostFromURI);
  3508         CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3509         if (priorCookieCount == 0) {
  3510           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3511                             aCookieHeader, "third party cookies are blocked "
  3512                             "for this site");
  3513           return STATUS_REJECTED;
  3515         return STATUS_ACCEPTED;
  3520   // check default prefs
  3521   if (mCookieBehavior == BEHAVIOR_REJECT) {
  3522     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
  3523     return STATUS_REJECTED;
  3526   // check if cookie is foreign
  3527   if (aIsForeign) {
  3528     if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
  3529       return STATUS_ACCEPT_SESSION;
  3531     if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
  3532       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3533       return STATUS_REJECTED;
  3536     if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) {
  3537       uint32_t priorCookieCount = 0;
  3538       nsAutoCString hostFromURI;
  3539       aHostURI->GetHost(hostFromURI);
  3540       CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3541       if (priorCookieCount == 0) {
  3542         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3543         return STATUS_REJECTED;
  3545       if (mThirdPartySession)
  3546         return STATUS_ACCEPT_SESSION;
  3550   // if nothing has complained, accept cookie
  3551   return STATUS_ACCEPTED;
  3554 // processes domain attribute, and returns true if host has permission to set for this domain.
  3555 bool
  3556 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
  3557                              nsIURI             *aHostURI,
  3558                              const nsCString    &aBaseDomain,
  3559                              bool                aRequireHostMatch)
  3561   // get host from aHostURI
  3562   nsAutoCString hostFromURI;
  3563   aHostURI->GetAsciiHost(hostFromURI);
  3565   // if a domain is given, check the host has permission
  3566   if (!aCookieAttributes.host.IsEmpty()) {
  3567     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
  3568     if (aCookieAttributes.host.Length() > 1 &&
  3569         aCookieAttributes.host.First() == '.') {
  3570       aCookieAttributes.host.Cut(0, 1);
  3573     // switch to lowercase now, to avoid case-insensitive compares everywhere
  3574     ToLowerCase(aCookieAttributes.host);
  3576     // check whether the host is either an IP address, an alias such as
  3577     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
  3578     // cases, require an exact string match for the domain, and leave the cookie
  3579     // as a non-domain one. bug 105917 originally noted the requirement to deal
  3580     // with IP addresses.
  3581     if (aRequireHostMatch)
  3582       return hostFromURI.Equals(aCookieAttributes.host);
  3584     // ensure the proposed domain is derived from the base domain; and also
  3585     // that the host domain is derived from the proposed domain (per RFC2109).
  3586     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
  3587         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
  3588       // prepend a dot to indicate a domain cookie
  3589       aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
  3590       return true;
  3593     /*
  3594      * note: RFC2109 section 4.3.2 requires that we check the following:
  3595      * that the portion of host not in domain does not contain a dot.
  3596      * this prevents hosts of the form x.y.co.nz from setting cookies in the
  3597      * entire .co.nz domain. however, it's only a only a partial solution and
  3598      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
  3599      */
  3600     return false;
  3603   // no domain specified, use hostFromURI
  3604   aCookieAttributes.host = hostFromURI;
  3605   return true;
  3608 bool
  3609 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
  3610                            nsIURI             *aHostURI)
  3612   // if a path is given, check the host has permission
  3613   if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
  3614     // strip down everything after the last slash to get the path,
  3615     // ignoring slashes in the query string part.
  3616     // if we can QI to nsIURL, that'll take care of the query string portion.
  3617     // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
  3618     nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
  3619     if (hostURL) {
  3620       hostURL->GetDirectory(aCookieAttributes.path);
  3621     } else {
  3622       aHostURI->GetPath(aCookieAttributes.path);
  3623       int32_t slash = aCookieAttributes.path.RFindChar('/');
  3624       if (slash != kNotFound) {
  3625         aCookieAttributes.path.Truncate(slash + 1);
  3629 #if 0
  3630   } else {
  3631     /**
  3632      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
  3633      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
  3634      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
  3635      * been disabled, unless we can evangelize these sites.
  3636      */
  3637     // get path from aHostURI
  3638     nsAutoCString pathFromURI;
  3639     if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
  3640         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
  3641       return false;
  3643 #endif
  3646   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
  3647       aCookieAttributes.path.FindChar('\t') != kNotFound )
  3648     return false;
  3650   return true;
  3653 bool
  3654 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
  3655                            int64_t             aServerTime,
  3656                            int64_t             aCurrentTime)
  3658   /* Determine when the cookie should expire. This is done by taking the difference between 
  3659    * the server time and the time the server wants the cookie to expire, and adding that 
  3660    * difference to the client time. This localizes the client time regardless of whether or
  3661    * not the TZ environment variable was set on the client.
  3663    * Note: We need to consider accounting for network lag here, per RFC.
  3664    */
  3665   int64_t delta;
  3667   // check for max-age attribute first; this overrides expires attribute
  3668   if (!aCookieAttributes.maxage.IsEmpty()) {
  3669     // obtain numeric value of maxageAttribute
  3670     int64_t maxage;
  3671     int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
  3673     // default to session cookie if the conversion failed
  3674     if (numInts != 1) {
  3675       return true;
  3678     delta = maxage;
  3680   // check for expires attribute
  3681   } else if (!aCookieAttributes.expires.IsEmpty()) {
  3682     PRTime expires;
  3684     // parse expiry time
  3685     if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
  3686       return true;
  3689     delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
  3691   // default to session cookie if no attributes found
  3692   } else {
  3693     return true;
  3696   // if this addition overflows, expiryTime will be less than currentTime
  3697   // and the cookie will be expired - that's okay.
  3698   aCookieAttributes.expiryTime = aCurrentTime + delta;
  3700   return false;
  3703 /******************************************************************************
  3704  * nsCookieService impl:
  3705  * private cookielist management functions
  3706  ******************************************************************************/
  3708 void
  3709 nsCookieService::RemoveAllFromMemory()
  3711   // clearing the hashtable will call each nsCookieEntry's dtor,
  3712   // which releases all their respective children.
  3713   mDBState->hostTable.Clear();
  3714   mDBState->cookieCount = 0;
  3715   mDBState->cookieOldestTime = INT64_MAX;
  3718 // stores temporary data for enumerating over the hash entries,
  3719 // since enumeration is done using callback functions
  3720 struct nsPurgeData
  3722   typedef nsTArray<nsListIter> ArrayType;
  3724   nsPurgeData(int64_t aCurrentTime,
  3725               int64_t aPurgeTime,
  3726               ArrayType &aPurgeList,
  3727               nsIMutableArray *aRemovedList,
  3728               mozIStorageBindingParamsArray *aParamsArray)
  3729    : currentTime(aCurrentTime)
  3730    , purgeTime(aPurgeTime)
  3731    , oldestTime(INT64_MAX)
  3732    , purgeList(aPurgeList)
  3733    , removedList(aRemovedList)
  3734    , paramsArray(aParamsArray)
  3738   // the current time, in seconds
  3739   int64_t currentTime;
  3741   // lastAccessed time older than which cookies are eligible for purge
  3742   int64_t purgeTime;
  3744   // lastAccessed time of the oldest cookie found during purge, to update our indicator
  3745   int64_t oldestTime;
  3747   // list of cookies over the age limit, for purging
  3748   ArrayType &purgeList;
  3750   // list of all cookies we've removed, for notification
  3751   nsIMutableArray *removedList;
  3753   // The array of parameters to be bound to the statement for deletion later.
  3754   mozIStorageBindingParamsArray *paramsArray;
  3755 };
  3757 // comparator class for lastaccessed times of cookies.
  3758 class CompareCookiesByAge {
  3759 public:
  3760   bool Equals(const nsListIter &a, const nsListIter &b) const
  3762     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
  3763            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
  3766   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3768     // compare by lastAccessed time, and tiebreak by creationTime.
  3769     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
  3770     if (result != 0)
  3771       return result < 0;
  3773     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
  3775 };
  3777 // comparator class for sorting cookies by entry and index.
  3778 class CompareCookiesByIndex {
  3779 public:
  3780   bool Equals(const nsListIter &a, const nsListIter &b) const
  3782     NS_ASSERTION(a.entry != b.entry || a.index != b.index,
  3783       "cookie indexes should never be equal");
  3784     return false;
  3787   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3789     // compare by entryclass pointer, then by index.
  3790     if (a.entry != b.entry)
  3791       return a.entry < b.entry;
  3793     return a.index < b.index;
  3795 };
  3797 PLDHashOperator
  3798 purgeCookiesCallback(nsCookieEntry *aEntry,
  3799                      void          *aArg)
  3801   nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
  3803   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3804   mozIStorageBindingParamsArray *array = data.paramsArray;
  3805   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
  3806     nsListIter iter(aEntry, i);
  3807     nsCookie *cookie = cookies[i];
  3809     // check if the cookie has expired
  3810     if (cookie->Expiry() <= data.currentTime) {
  3811       data.removedList->AppendElement(cookie, false);
  3812       COOKIE_LOGEVICTED(cookie, "Cookie expired");
  3814       // remove from list; do not increment our iterator
  3815       gCookieService->RemoveCookieFromList(iter, array);
  3817     } else {
  3818       // check if the cookie is over the age limit
  3819       if (cookie->LastAccessed() <= data.purgeTime) {
  3820         data.purgeList.AppendElement(iter);
  3822       } else if (cookie->LastAccessed() < data.oldestTime) {
  3823         // reset our indicator
  3824         data.oldestTime = cookie->LastAccessed();
  3827       ++i;
  3830   return PL_DHASH_NEXT;
  3833 // purges expired and old cookies in a batch operation.
  3834 already_AddRefed<nsIArray>
  3835 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
  3837   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
  3838   EnsureReadComplete();
  3840 #ifdef PR_LOGGING
  3841   uint32_t initialCookieCount = mDBState->cookieCount;
  3842   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3843     ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
  3844      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3845 #endif
  3847   nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
  3849   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
  3851   // Create a params array to batch the removals. This is OK here because
  3852   // all the removals are in order, and there are no interleaved additions.
  3853   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  3854   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  3855   if (mDBState->dbConn) {
  3856     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  3859   nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
  3860     aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
  3861   mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
  3863 #ifdef PR_LOGGING
  3864   uint32_t postExpiryCookieCount = mDBState->cookieCount;
  3865 #endif
  3867   // now we have a list of iterators for cookies over the age limit.
  3868   // sort them by age, and then we'll see how many to remove...
  3869   purgeList.Sort(CompareCookiesByAge());
  3871   // only remove old cookies until we reach the max cookie limit, no more.
  3872   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
  3873     mDBState->cookieCount - mMaxNumberOfCookies : 0;
  3874   if (purgeList.Length() > excess) {
  3875     // We're not purging everything in the list, so update our indicator.
  3876     data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
  3878     purgeList.SetLength(excess);
  3881   // sort the list again, this time grouping cookies with a common entryclass
  3882   // together, and with ascending index. this allows us to iterate backwards
  3883   // over the list removing cookies, without having to adjust indexes as we go.
  3884   purgeList.Sort(CompareCookiesByIndex());
  3885   for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
  3886     nsCookie *cookie = purgeList[i].Cookie();
  3887     removedList->AppendElement(cookie, false);
  3888     COOKIE_LOGEVICTED(cookie, "Cookie too old");
  3890     RemoveCookieFromList(purgeList[i], paramsArray);
  3893   // Update the database if we have entries to purge.
  3894   if (paramsArray) {
  3895     uint32_t length;
  3896     paramsArray->GetLength(&length);
  3897     if (length) {
  3898       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  3899       NS_ASSERT_SUCCESS(rv);
  3900       nsCOMPtr<mozIStoragePendingStatement> handle;
  3901       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  3902       NS_ASSERT_SUCCESS(rv);
  3906   // reset the oldest time indicator
  3907   mDBState->cookieOldestTime = data.oldestTime;
  3909   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3910     ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
  3911      initialCookieCount - postExpiryCookieCount,
  3912      postExpiryCookieCount - mDBState->cookieCount,
  3913      mDBState->cookieCount,
  3914      aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3916   return removedList.forget();
  3919 // find whether a given cookie has been previously set. this is provided by the
  3920 // nsICookieManager2 interface.
  3921 NS_IMETHODIMP
  3922 nsCookieService::CookieExists(nsICookie2 *aCookie,
  3923                               bool       *aFoundCookie)
  3925   NS_ENSURE_ARG_POINTER(aCookie);
  3927   if (!mDBState) {
  3928     NS_WARNING("No DBState! Profile already closed?");
  3929     return NS_ERROR_NOT_AVAILABLE;
  3932   nsAutoCString host, origin, name, path;
  3933   nsresult rv = aCookie->GetHost(host);
  3934   NS_ENSURE_SUCCESS(rv, rv);
  3935   rv = aCookie->GetOrigin(origin);
  3936   NS_ENSURE_SUCCESS(rv, rv);
  3937   rv = aCookie->GetName(name);
  3938   NS_ENSURE_SUCCESS(rv, rv);
  3939   rv = aCookie->GetPath(path);
  3940   NS_ENSURE_SUCCESS(rv, rv);
  3942   nsAutoCString baseDomain;
  3943   rv = GetBaseDomainFromHost(host, baseDomain);
  3944   NS_ENSURE_SUCCESS(rv, rv);
  3946   nsListIter iter;
  3947   *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), origin, host, name, path, iter);
  3948   return NS_OK;
  3951 // For a given base domain, find either an expired cookie or the oldest cookie
  3952 // by lastAccessed time.
  3953 void
  3954 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
  3955                                  int64_t aCurrentTime,
  3956                                  nsListIter &aIter)
  3958   aIter.entry = nullptr;
  3960   int64_t oldestTime = 0;
  3961   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3962   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  3963     nsCookie *cookie = cookies[i];
  3965     // If we found an expired cookie, we're done.
  3966     if (cookie->Expiry() <= aCurrentTime) {
  3967       aIter.entry = aEntry;
  3968       aIter.index = i;
  3969       return;
  3972     // Check if we've found the oldest cookie so far.
  3973     if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
  3974       oldestTime = cookie->LastAccessed();
  3975       aIter.entry = aEntry;
  3976       aIter.index = i;
  3981 // count the number of cookies stored by a particular host. this is provided by the
  3982 // nsICookieManager2 interface.
  3983 NS_IMETHODIMP
  3984 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
  3985                                       uint32_t         *aCountFromHost)
  3987   if (!mDBState) {
  3988     NS_WARNING("No DBState! Profile already closed?");
  3989     return NS_ERROR_NOT_AVAILABLE;
  3992   // first, normalize the hostname, and fail if it contains illegal characters.
  3993   nsAutoCString host(aHost);
  3994   nsresult rv = NormalizeHost(host);
  3995   NS_ENSURE_SUCCESS(rv, rv);
  3997   nsAutoCString baseDomain;
  3998   rv = GetBaseDomainFromHost(host, baseDomain);
  3999   NS_ENSURE_SUCCESS(rv, rv);
  4001   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  4002   EnsureReadDomain(key);
  4004   // Return a count of all cookies, including expired.
  4005   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  4006   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
  4007   return NS_OK;
  4010 // get an enumerator of cookies stored by a particular host. this is provided by the
  4011 // nsICookieManager2 interface.
  4012 NS_IMETHODIMP
  4013 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
  4014                                     nsISimpleEnumerator **aEnumerator)
  4016   if (!mDBState) {
  4017     NS_WARNING("No DBState! Profile already closed?");
  4018     return NS_ERROR_NOT_AVAILABLE;
  4021   // first, normalize the hostname, and fail if it contains illegal characters.
  4022   nsAutoCString host(aHost);
  4023   nsresult rv = NormalizeHost(host);
  4024   NS_ENSURE_SUCCESS(rv, rv);
  4026   nsAutoCString baseDomain;
  4027   rv = GetBaseDomainFromHost(host, baseDomain);
  4028   NS_ENSURE_SUCCESS(rv, rv);
  4030   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  4031   EnsureReadDomain(key);
  4033   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  4034   if (!entry)
  4035     return NS_NewEmptyEnumerator(aEnumerator);
  4037   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
  4038   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  4039   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4040     cookieList.AppendObject(cookies[i]);
  4043   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  4046 namespace {
  4048 /**
  4049  * This structure is used as a in/out parameter when enumerating the cookies
  4050  * for an app.
  4051  * It will contain the app id and onlyBrowserElement flag information as input
  4052  * and will contain the array of matching cookies as output.
  4053  */
  4054 struct GetCookiesForAppStruct {
  4055   uint32_t              appId;
  4056   bool                  onlyBrowserElement;
  4057   nsCOMArray<nsICookie> cookies;
  4059   GetCookiesForAppStruct() MOZ_DELETE;
  4060   GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
  4061     : appId(aAppId)
  4062     , onlyBrowserElement(aOnlyBrowserElement)
  4063   {}
  4064 };
  4066 } // anonymous namespace
  4068 /* static */ PLDHashOperator
  4069 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
  4071   GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
  4073   if (entry->mAppId != data->appId ||
  4074       (data->onlyBrowserElement && !entry->mInBrowserElement)) {
  4075     return PL_DHASH_NEXT;
  4078   const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
  4080   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4081     data->cookies.AppendObject(cookies[i]);
  4084   return PL_DHASH_NEXT;
  4087 NS_IMETHODIMP
  4088 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
  4089                                   nsISimpleEnumerator** aEnumerator)
  4091   if (!mDBState) {
  4092     NS_WARNING("No DBState! Profile already closed?");
  4093     return NS_ERROR_NOT_AVAILABLE;
  4096   NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
  4098   GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
  4099   mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
  4101   return NS_NewArrayEnumerator(aEnumerator, data.cookies);
  4104 NS_IMETHODIMP
  4105 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
  4107   nsCOMPtr<nsISimpleEnumerator> enumerator;
  4108   nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
  4109                                  getter_AddRefs(enumerator));
  4111   NS_ENSURE_SUCCESS(rv, rv);
  4113   bool hasMore;
  4114   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
  4115     nsCOMPtr<nsISupports> supports;
  4116     nsCOMPtr<nsICookie> cookie;
  4117     rv = enumerator->GetNext(getter_AddRefs(supports));
  4118     NS_ENSURE_SUCCESS(rv, rv);
  4120     cookie = do_QueryInterface(supports);
  4122     nsAutoCString host;
  4123     cookie->GetHost(host);
  4125     nsAutoCString name;
  4126     cookie->GetName(name);
  4128     nsAutoCString path;
  4129     cookie->GetPath(path);
  4131     // nsICookie do not carry the appId/inBrowserElement information.
  4132     // That means we have to guess. This is easy for appId but not for
  4133     // inBrowserElement flag.
  4134     // A simple solution is to always ask to remove the cookie with
  4135     // inBrowserElement = true and only ask for the other one to be removed if
  4136     // we happen to be in the case of !aOnlyBrowserElement.
  4137     // Anyway, with this solution, we will likely be looking for unexistant
  4138     // cookies.
  4139     //
  4140     // NOTE: we could make this better by getting nsCookieEntry objects instead
  4141     // of plain nsICookie.
  4142     Remove(host, aAppId, true, name, path, false);
  4143     if (!aOnlyBrowserElement) {
  4144       Remove(host, aAppId, false, name, path, false);
  4148   return NS_OK;
  4151 // find an exact cookie specified by host, name, and path that hasn't expired.
  4152 bool
  4153 nsCookieService::FindCookie(const nsCookieKey    &aKey,
  4154                             const nsAFlatCString &aOrigin,
  4155                             const nsAFlatCString &aHost,
  4156                             const nsAFlatCString &aName,
  4157                             const nsAFlatCString &aPath,
  4158                             nsListIter           &aIter)
  4160   EnsureReadDomain(aKey);
  4162   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  4163   if (!entry)
  4164     return false;
  4166   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  4167   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4168     nsCookie *cookie = cookies[i];
  4170     if (aOrigin.Equals(cookie->Origin()) &&
  4171         aHost.Equals(cookie->Host()) &&
  4172         aPath.Equals(cookie->Path()) &&
  4173         aName.Equals(cookie->Name())) {
  4174       aIter = nsListIter(entry, i);
  4175       return true;
  4179   return false;
  4182 // remove a cookie from the hashtable, and update the iterator state.
  4183 void
  4184 nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
  4185                                       mozIStorageBindingParamsArray *aParamsArray)
  4187   // if it's a non-session cookie, remove it from the db
  4188   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
  4189     // Use the asynchronous binding methods to ensure that we do not acquire
  4190     // the database lock.
  4191     mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  4192     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4193     if (!paramsArray) {
  4194       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4197     nsCOMPtr<mozIStorageBindingParams> params;
  4198     paramsArray->NewBindingParams(getter_AddRefs(params));
  4200     DebugOnly<nsresult> rv =
  4201       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4202                                    aIter.Cookie()->Name());
  4203     NS_ASSERT_SUCCESS(rv);
  4205     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4206                                       aIter.Cookie()->Host());
  4207     NS_ASSERT_SUCCESS(rv);
  4209     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4210                                       aIter.Cookie()->Path());
  4211     NS_ASSERT_SUCCESS(rv);
  4213     rv = paramsArray->AddParams(params);
  4214     NS_ASSERT_SUCCESS(rv);
  4216     // If we weren't given a params array, we'll need to remove it ourselves.
  4217     if (!aParamsArray) {
  4218       rv = stmt->BindParameters(paramsArray);
  4219       NS_ASSERT_SUCCESS(rv);
  4220       nsCOMPtr<mozIStoragePendingStatement> handle;
  4221       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  4222       NS_ASSERT_SUCCESS(rv);
  4226   if (aIter.entry->GetCookies().Length() == 1) {
  4227     // we're removing the last element in the array - so just remove the entry
  4228     // from the hash. note that the entryclass' dtor will take care of
  4229     // releasing this last element for us!
  4230     mDBState->hostTable.RawRemoveEntry(aIter.entry);
  4232   } else {
  4233     // just remove the element from the list
  4234     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
  4237   --mDBState->cookieCount;
  4240 void
  4241 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
  4242                      const nsCookieKey &aKey,
  4243                      const nsCookie *aCookie)
  4245   NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
  4246   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
  4248   // Use the asynchronous binding methods to ensure that we do not acquire the
  4249   // database lock.
  4250   nsCOMPtr<mozIStorageBindingParams> params;
  4251   DebugOnly<nsresult> rv =
  4252     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4253   NS_ASSERT_SUCCESS(rv);
  4255   // Bind our values to params
  4256   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
  4257                                     aKey.mBaseDomain);
  4258   NS_ASSERT_SUCCESS(rv);
  4260   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
  4261                                aKey.mAppId);
  4262   NS_ASSERT_SUCCESS(rv);
  4264   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
  4265                                aKey.mInBrowserElement ? 1 : 0);
  4266   NS_ASSERT_SUCCESS(rv);
  4268   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4269                                     aCookie->Name());
  4270   NS_ASSERT_SUCCESS(rv);
  4272   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
  4273                                     aCookie->Value());
  4274   NS_ASSERT_SUCCESS(rv);
  4276   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4277                                     aCookie->Host());
  4278   NS_ASSERT_SUCCESS(rv);
  4280   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4281                                     aCookie->Path());
  4282   NS_ASSERT_SUCCESS(rv);
  4284   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
  4285                                aCookie->Expiry());
  4286   NS_ASSERT_SUCCESS(rv);
  4288   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4289                                aCookie->LastAccessed());
  4290   NS_ASSERT_SUCCESS(rv);
  4292   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
  4293                                aCookie->CreationTime());
  4294   NS_ASSERT_SUCCESS(rv);
  4296   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
  4297                                aCookie->IsSecure());
  4298   NS_ASSERT_SUCCESS(rv);
  4300   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
  4301                                aCookie->IsHttpOnly());
  4302   NS_ASSERT_SUCCESS(rv);
  4304   // Bind the params to the array.
  4305   rv = aParamsArray->AddParams(params);
  4306   NS_ASSERT_SUCCESS(rv);
  4309 void
  4310 nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
  4311                                  nsCookie                      *aCookie,
  4312                                  DBState                       *aDBState,
  4313                                  mozIStorageBindingParamsArray *aParamsArray,
  4314                                  bool                           aWriteToDB)
  4316   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
  4317                "Not writing to the DB but have a params array?");
  4318   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
  4319                "Do not have a DB connection but have a params array?");
  4321   nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
  4322   NS_ASSERTION(entry, "can't insert element into a null entry!");
  4324   entry->GetCookies().AppendElement(aCookie);
  4325   ++aDBState->cookieCount;
  4327   // keep track of the oldest cookie, for when it comes time to purge
  4328   if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
  4329     aDBState->cookieOldestTime = aCookie->LastAccessed();
  4331   // if it's a non-session cookie and hasn't just been read from the db, write it out.
  4332   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
  4333     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
  4334     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4335     if (!paramsArray) {
  4336       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4338     bindCookieParameters(paramsArray, aKey, aCookie);
  4340     // If we were supplied an array to store parameters, we shouldn't call
  4341     // executeAsync - someone up the stack will do this for us.
  4342     if (!aParamsArray) {
  4343       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  4344       NS_ASSERT_SUCCESS(rv);
  4345       nsCOMPtr<mozIStoragePendingStatement> handle;
  4346       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
  4347       NS_ASSERT_SUCCESS(rv);
  4352 void
  4353 nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
  4354                                     int64_t                        aLastAccessed,
  4355                                     mozIStorageBindingParamsArray *aParamsArray)
  4357   NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
  4359   // udpate the lastAccessed timestamp
  4360   aCookie->SetLastAccessed(aLastAccessed);
  4362   // if it's a non-session cookie, update it in the db too
  4363   if (!aCookie->IsSession() && aParamsArray) {
  4364     // Create our params holder.
  4365     nsCOMPtr<mozIStorageBindingParams> params;
  4366     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4368     // Bind our parameters.
  4369     DebugOnly<nsresult> rv =
  4370       params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4371                               aLastAccessed);
  4372     NS_ASSERT_SUCCESS(rv);
  4374     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4375                                       aCookie->Name());
  4376     NS_ASSERT_SUCCESS(rv);
  4378     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4379                                       aCookie->Host());
  4380     NS_ASSERT_SUCCESS(rv);
  4382     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4383                                       aCookie->Path());
  4384     NS_ASSERT_SUCCESS(rv);
  4386     // Add our bound parameters to the array.
  4387     rv = aParamsArray->AddParams(params);
  4388     NS_ASSERT_SUCCESS(rv);
  4392 size_t
  4393 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  4395   size_t n = aMallocSizeOf(this);
  4397   if (mDefaultDBState) {
  4398     n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
  4400   if (mPrivateDBState) {
  4401     n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
  4404   return n;
  4407 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
  4409 NS_IMETHODIMP
  4410 nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
  4411                                 nsISupports* aData)
  4413   return MOZ_COLLECT_REPORT(
  4414     "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
  4415     SizeOfIncludingThis(CookieServiceMallocSizeOf),
  4416     "Memory used by the cookie service.");

mercurial