netwerk/cookie/nsCookieService.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
parent 4
fc2d59ddac77
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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   // Double keying: First get the first party URI
  1639   nsCOMPtr<nsIURI> firstPartyURI;
  1640   nsAutoCString origDomain;
  1641   bool requireHostMatch;
  1642   nsresult rv = mThirdPartyUtil->GetFirstPartyIsolationURI(aChannel, nullptr, getter_AddRefs(firstPartyURI));
  1643   if (firstPartyURI) {
  1644     // Double keying: Now get the originating domain
  1645     rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
  1646     if (NS_FAILED(rv)) {
  1647       COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
  1648                         "couldn't get base domain from URI");
  1651   //else if (NS_SUCCEEDED(rv)) {}; // Not reached, we got a originating domain!
  1653   nsAutoCString result;
  1654   GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
  1655                           inBrowserElement, isPrivate, origDomain, result);
  1656   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
  1657   return NS_OK;
  1660 NS_IMETHODIMP
  1661 nsCookieService::SetCookieString(nsIURI     *aHostURI,
  1662                                  nsIPrompt  *aPrompt,
  1663                                  const char *aCookieHeader,
  1664                                  nsIChannel *aChannel)
  1666   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1667   // code that uses this argument by warning if the value is non-null.
  1668   MOZ_ASSERT(!aPrompt);
  1669   if (aPrompt) {
  1670     nsCOMPtr<nsIConsoleService> aConsoleService =
  1671         do_GetService("@mozilla.org/consoleservice;1");
  1672     if (aConsoleService) {
  1673       aConsoleService->LogStringMessage(
  1674         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1677   return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
  1678                                false);
  1681 NS_IMETHODIMP
  1682 nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
  1683                                          nsIURI     *aFirstURI,
  1684                                          nsIPrompt  *aPrompt,
  1685                                          const char *aCookieHeader,
  1686                                          const char *aServerTime,
  1687                                          nsIChannel *aChannel) 
  1689   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1690   // code that uses this argument by warning if the value is non-null.
  1691   MOZ_ASSERT(!aPrompt);
  1692   if (aPrompt) {
  1693     nsCOMPtr<nsIConsoleService> aConsoleService =
  1694         do_GetService("@mozilla.org/consoleservice;1");
  1695     if (aConsoleService) {
  1696       aConsoleService->LogStringMessage(
  1697         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1700   return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
  1701                                true);
  1704 nsresult
  1705 nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
  1706                                        const char *aCookieHeader,
  1707                                        const char *aServerTime,
  1708                                        nsIChannel *aChannel,
  1709                                        bool aFromHttp) 
  1711   NS_ENSURE_ARG(aHostURI);
  1712   NS_ENSURE_ARG(aCookieHeader);
  1714   // Determine whether the request is foreign. Failure is acceptable.
  1715   bool isForeign = true;
  1716   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1718   // Get app info, if channel is present.  Else assume default namespace.
  1719   uint32_t appId = NECKO_NO_APP_ID;
  1720   bool inBrowserElement = false;
  1721   if (aChannel) {
  1722     NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1725   bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1727   nsDependentCString cookieString(aCookieHeader);
  1728   nsDependentCString serverTime(aServerTime ? aServerTime : "");
  1729   SetCookieStringInternal(aHostURI, isForeign, cookieString,
  1730                           serverTime, aFromHttp, appId, inBrowserElement,
  1731                           isPrivate, aChannel);
  1732   return NS_OK;
  1735 // FIXME:MSvB DEBUG DEBUG - DELETEME DELETEME - debug debug - deleteme deleteme
  1736 // FIXME:MSvB Setting a 3rd party cookie (on third.tld) for URL bar browsed
  1737 // FIXME:MSvB   site first.tld causes aHostURI (and later the origin var) to
  1738 // FIXME:MSvB   contain 'third.tld'
  1739 void
  1740 nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
  1741                                          bool                aIsForeign,
  1742                                          nsDependentCString &aCookieHeader,
  1743                                          const nsCString    &aServerTime,
  1744                                          bool                aFromHttp,
  1745                                          uint32_t            aAppId,
  1746                                          bool                aInBrowserElement,
  1747                                          bool                aIsPrivate,
  1748                                          nsIChannel         *aChannel)
  1750   NS_ASSERTION(aHostURI, "null host!");
  1752   if (!mDBState) {
  1753     NS_WARNING("No DBState! Profile already closed?");
  1754     return;
  1757   AutoRestore<DBState*> savePrevDBState(mDBState);
  1758   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  1760   // get the base domain for the host URI.
  1761   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  1762   // file:// URI's (i.e. with an empty host) are allowed, but any other
  1763   // scheme must have a non-empty host. A trailing dot in the host
  1764   // is acceptable.
  1765   bool requireHostMatch;
  1766   nsAutoCString baseDomain;
  1767   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  1768   if (NS_FAILED(rv)) {
  1769     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
  1770                       "couldn't get base domain from URI");
  1771     return;
  1774   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  1776   // check default prefs
  1777   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  1778                                          aCookieHeader.get());
  1779   // fire a notification if third party or if cookie was rejected
  1780   // (but not if there was an error)
  1781   switch (cookieStatus) {
  1782   case STATUS_REJECTED:
  1783     NotifyRejected(aHostURI);
  1784     if (aIsForeign) {
  1785       NotifyThirdParty(aHostURI, false, aChannel);
  1787     return; // Stop here
  1788   case STATUS_REJECTED_WITH_ERROR:
  1789     return;
  1790   case STATUS_ACCEPTED: // Fallthrough
  1791   case STATUS_ACCEPT_SESSION:
  1792     if (aIsForeign) {
  1793       NotifyThirdParty(aHostURI, true, aChannel);
  1795     break;
  1796   default:
  1797     break;
  1800   // parse server local time. this is not just done here for efficiency
  1801   // reasons - if there's an error parsing it, and we need to default it
  1802   // to the current time, we must do it here since the current time in
  1803   // SetCookieInternal() will change for each cookie processed (e.g. if the
  1804   // user is prompted).
  1805   PRTime tempServerTime;
  1806   int64_t serverTime;
  1807   PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
  1808                                        &tempServerTime);
  1809   if (result == PR_SUCCESS) {
  1810     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
  1811   } else {
  1812     serverTime = PR_Now() / PR_USEC_PER_SEC;
  1815   // double keyed cookie boilerplate
  1816   nsCOMPtr<nsIURI> firstPartyURI;
  1817   mThirdPartyUtil->GetFirstPartyURIFromChannel(aChannel, true, getter_AddRefs(firstPartyURI));
  1818   NS_ASSERTION(firstPartyURI, "couldn't get the first party URI");
  1819   nsAutoCString origDomain;
  1820   rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
  1821   if (NS_FAILED(rv)) {
  1822     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
  1823                       "couldn't get base domain from URI");
  1826   // process each cookie in the header
  1827   while (SetCookieInternal(aHostURI, key, requireHostMatch, origDomain,
  1828                            cookieStatus, aCookieHeader, serverTime,
  1829                            aFromHttp, aChannel)) {
  1830     // document.cookie can only set one cookie at a time
  1831     if (!aFromHttp)
  1832       break;
  1836 // notify observers that a cookie was rejected due to the users' prefs.
  1837 void
  1838 nsCookieService::NotifyRejected(nsIURI *aHostURI)
  1840   if (mObserverService) {
  1841     mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
  1845 // notify observers that a third-party cookie was accepted/rejected
  1846 // if the cookie issuer is unknown, it defaults to "?"
  1847 void
  1848 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
  1850   if (!mObserverService) {
  1851     return;
  1854   const char* topic;
  1856   if (mDBState != mPrivateDBState) {
  1857     // Regular (non-private) browsing
  1858     if (aIsAccepted) {
  1859       topic = "third-party-cookie-accepted";
  1860     } else {
  1861       topic = "third-party-cookie-rejected";
  1863   } else {
  1864     // Private browsing
  1865     if (aIsAccepted) {
  1866       topic = "private-third-party-cookie-accepted";
  1867     } else {
  1868       topic = "private-third-party-cookie-rejected";
  1872   do {
  1873     // Attempt to find the host of aChannel.
  1874     if (!aChannel) {
  1875       break;
  1877     nsCOMPtr<nsIURI> channelURI;
  1878     nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
  1879     if (NS_FAILED(rv)) {
  1880       break;
  1883     nsAutoCString referringHost;
  1884     rv = channelURI->GetHost(referringHost);
  1885     if (NS_FAILED(rv)) {
  1886       break;
  1889     nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
  1890     mObserverService->NotifyObservers(aHostURI,
  1891                                       topic,
  1892                                       referringHostUTF16.get());
  1893     return;
  1894   } while (0);
  1896   // This can fail for a number of reasons, in which kind we fallback to "?"
  1897   mObserverService->NotifyObservers(aHostURI,
  1898                                     topic,
  1899                                     MOZ_UTF16("?"));
  1902 // notify observers that the cookie list changed. there are five possible
  1903 // values for aData:
  1904 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
  1905 // "added"   means a cookie was added. aSubject is the added cookie.
  1906 // "changed" means a cookie was altered. aSubject is the new cookie.
  1907 // "cleared" means the entire cookie list was cleared. aSubject is null.
  1908 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
  1909 // cookies.
  1910 void
  1911 nsCookieService::NotifyChanged(nsISupports     *aSubject,
  1912                                const char16_t *aData)
  1914   const char* topic = mDBState == mPrivateDBState ?
  1915       "private-cookie-changed" : "cookie-changed";
  1916   if (mObserverService)
  1917     mObserverService->NotifyObservers(aSubject, topic, aData);
  1920 already_AddRefed<nsIArray>
  1921 nsCookieService::CreatePurgeList(nsICookie2* aCookie)
  1923   nsCOMPtr<nsIMutableArray> removedList =
  1924     do_CreateInstance(NS_ARRAY_CONTRACTID);
  1925   removedList->AppendElement(aCookie, false);
  1926   return removedList.forget();
  1929 /******************************************************************************
  1930  * nsCookieService:
  1931  * pref observer impl
  1932  ******************************************************************************/
  1934 void
  1935 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
  1937   int32_t val;
  1938   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
  1939     mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
  1941   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
  1942     mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
  1944   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
  1945     mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
  1947   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
  1948     mCookiePurgeAge =
  1949       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
  1952   bool boolval;
  1953   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
  1954     mThirdPartySession = boolval;
  1957 /******************************************************************************
  1958  * nsICookieManager impl:
  1959  * nsICookieManager
  1960  ******************************************************************************/
  1962 NS_IMETHODIMP
  1963 nsCookieService::RemoveAll()
  1965   if (!mDBState) {
  1966     NS_WARNING("No DBState! Profile already closed?");
  1967     return NS_ERROR_NOT_AVAILABLE;
  1970   RemoveAllFromMemory();
  1972   // clear the cookie file
  1973   if (mDBState->dbConn) {
  1974     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
  1976     // Cancel any pending read. No further results will be received by our
  1977     // read listener.
  1978     if (mDefaultDBState->pendingRead) {
  1979       CancelAsyncRead(true);
  1982     nsCOMPtr<mozIStorageAsyncStatement> stmt;
  1983     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1984       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
  1985     if (NS_SUCCEEDED(rv)) {
  1986       nsCOMPtr<mozIStoragePendingStatement> handle;
  1987       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
  1988         getter_AddRefs(handle));
  1989       NS_ASSERT_SUCCESS(rv);
  1990     } else {
  1991       // Recreate the database.
  1992       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1993         ("RemoveAll(): corruption detected with rv 0x%x", rv));
  1994       HandleCorruptDB(mDefaultDBState);
  1998   NotifyChanged(nullptr, MOZ_UTF16("cleared"));
  1999   return NS_OK;
  2002 static PLDHashOperator
  2003 COMArrayCallback(nsCookieEntry *aEntry,
  2004                  void          *aArg)
  2006   nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
  2008   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  2009   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  2010     data->AppendObject(cookies[i]);
  2013   return PL_DHASH_NEXT;
  2016 NS_IMETHODIMP
  2017 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
  2019   if (!mDBState) {
  2020     NS_WARNING("No DBState! Profile already closed?");
  2021     return NS_ERROR_NOT_AVAILABLE;
  2024   EnsureReadComplete();
  2026   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
  2027   mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
  2029   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  2032 NS_IMETHODIMP
  2033 nsCookieService::Add(const nsACString &aHost,
  2034                      const nsACString &aPath,
  2035                      const nsACString &aName,
  2036                      const nsACString &aValue,
  2037                      bool              aIsSecure,
  2038                      bool              aIsHttpOnly,
  2039                      bool              aIsSession,
  2040                      int64_t           aExpiry)
  2042   if (!mDBState) {
  2043     NS_WARNING("No DBState! Profile already closed?");
  2044     return NS_ERROR_NOT_AVAILABLE;
  2047   // first, normalize the hostname, and fail if it contains illegal characters.
  2048   nsAutoCString host(aHost);
  2049   nsresult rv = NormalizeHost(host);
  2050   NS_ENSURE_SUCCESS(rv, rv);
  2052   // get the base domain for the host URI.
  2053   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  2054   nsAutoCString baseDomain;
  2055   rv = GetBaseDomainFromHost(host, baseDomain);
  2056   NS_ENSURE_SUCCESS(rv, rv);
  2058   int64_t currentTimeInUsec = PR_Now();
  2060   nsRefPtr<nsCookie> cookie =
  2061     nsCookie::Create(aName, aValue, host, baseDomain, aPath,
  2062                      aExpiry,
  2063                      currentTimeInUsec,
  2064                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2065                      aIsSession,
  2066                      aIsSecure,
  2067                      aIsHttpOnly);
  2068   if (!cookie) {
  2069     return NS_ERROR_OUT_OF_MEMORY;
  2072   AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
  2073   return NS_OK;
  2077 nsresult
  2078 nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId,
  2079                         bool aInBrowserElement, const nsACString& aName,
  2080                         const nsACString& aPath, bool aBlocked)
  2082   if (!mDBState) {
  2083     NS_WARNING("No DBState! Profile already closed?");
  2084     return NS_ERROR_NOT_AVAILABLE;
  2087   // first, normalize the hostname, and fail if it contains illegal characters.
  2088   nsAutoCString host(aHost);
  2089   nsresult rv = NormalizeHost(host);
  2090   NS_ENSURE_SUCCESS(rv, rv);
  2092   nsAutoCString baseDomain;
  2093   rv = GetBaseDomainFromHost(host, baseDomain);
  2094   NS_ENSURE_SUCCESS(rv, rv);
  2096   nsListIter matchIter;
  2097   nsRefPtr<nsCookie> cookie;
  2098   if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement),
  2099                  baseDomain,
  2100                  host,
  2101                  PromiseFlatCString(aName),
  2102                  PromiseFlatCString(aPath),
  2103                  matchIter)) {
  2104     cookie = matchIter.Cookie();
  2105     RemoveCookieFromList(matchIter);
  2108   // check if we need to add the host to the permissions blacklist.
  2109   if (aBlocked && mPermissionService) {
  2110     // strip off the domain dot, if necessary
  2111     if (!host.IsEmpty() && host.First() == '.')
  2112       host.Cut(0, 1);
  2114     host.Insert(NS_LITERAL_CSTRING("http://"), 0);
  2116     nsCOMPtr<nsIURI> uri;
  2117     NS_NewURI(getter_AddRefs(uri), host);
  2119     if (uri)
  2120       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
  2123   if (cookie) {
  2124     // Everything's done. Notify observers.
  2125     NotifyChanged(cookie, MOZ_UTF16("deleted"));
  2128   return NS_OK;
  2131 NS_IMETHODIMP
  2132 nsCookieService::Remove(const nsACString &aHost,
  2133                         const nsACString &aName,
  2134                         const nsACString &aPath,
  2135                         bool             aBlocked)
  2137   return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked);
  2140 /******************************************************************************
  2141  * nsCookieService impl:
  2142  * private file I/O functions
  2143  ******************************************************************************/
  2145 // Begin an asynchronous read from the database.
  2146 OpenDBResult
  2147 nsCookieService::Read()
  2149   // Set up a statement for the read. Note that our query specifies that
  2150   // 'baseDomain' not be nullptr -- see below for why.
  2151   nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
  2152   nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2153     "SELECT "
  2154       "name, "
  2155       "value, "
  2156       "host, "
  2157       "path, "
  2158       "expiry, "
  2159       "lastAccessed, "
  2160       "creationTime, "
  2161       "isSecure, "
  2162       "isHttpOnly, "
  2163       "baseDomain, "
  2164       "appId,  "
  2165       "inBrowserElement "
  2166     "FROM moz_cookies "
  2167     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
  2168   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2170   // Set up a statement to delete any rows with a nullptr 'baseDomain'
  2171   // column. This takes care of any cookies set by browsers that don't
  2172   // understand the 'baseDomain' column, where the database schema version
  2173   // is from one that does. (This would occur when downgrading.)
  2174   nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
  2175   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2176     "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
  2177     getter_AddRefs(stmtDeleteNull));
  2178   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2180   // Start a new connection for sync reads, to reduce contention with the
  2181   // background thread. We need to do this before we kick off write statements,
  2182   // since they can lock the database and prevent connections from being opened.
  2183   rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
  2184     getter_AddRefs(mDefaultDBState->syncConn));
  2185   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2187   // Init our readSet hash and execute the statements. Note that, after this
  2188   // point, we cannot fail without altering the cleanup code in InitDBStates()
  2189   // to handle closing of the now-asynchronous connection.
  2190   mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
  2192   mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
  2193   rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
  2194     getter_AddRefs(mDefaultDBState->pendingRead));
  2195   NS_ASSERT_SUCCESS(rv);
  2197   nsCOMPtr<mozIStoragePendingStatement> handle;
  2198   rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
  2199     getter_AddRefs(handle));
  2200   NS_ASSERT_SUCCESS(rv);
  2202   return RESULT_OK;
  2205 // Extract data from a single result row and create an nsCookie.
  2206 // This is templated since 'T' is different for sync vs async results.
  2207 template<class T> nsCookie*
  2208 nsCookieService::GetCookieFromRow(T &aRow)
  2210   // Skip reading 'baseDomain' -- up to the caller.
  2211   nsCString name, value, host, path;
  2212   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
  2213   NS_ASSERT_SUCCESS(rv);
  2214   rv = aRow->GetUTF8String(IDX_VALUE, value);
  2215   NS_ASSERT_SUCCESS(rv);
  2216   rv = aRow->GetUTF8String(IDX_HOST, host);
  2217   NS_ASSERT_SUCCESS(rv);
  2218   rv = aRow->GetUTF8String(IDX_PATH, path);
  2219   NS_ASSERT_SUCCESS(rv);
  2221   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
  2222   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
  2223   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
  2224   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
  2225   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
  2227   nsAutoCString baseDomain;
  2228   rv = GetBaseDomainFromHost(host, baseDomain);
  2229   NS_ASSERT_SUCCESS(rv);
  2231   // Create a new nsCookie and assign the data.
  2232   return nsCookie::Create(name, value, host, baseDomain, path,
  2233                           expiry,
  2234                           lastAccessed,
  2235                           creationTime,
  2236                           false,
  2237                           isSecure,
  2238                           isHttpOnly);
  2241 void
  2242 nsCookieService::AsyncReadComplete()
  2244   // We may be in the private browsing DB state, with a pending read on the
  2245   // default DB state. (This would occur if we started up in private browsing
  2246   // mode.) As long as we do all our operations on the default state, we're OK.
  2247   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2248   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2249   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2251   // Merge the data read on the background thread with the data synchronously
  2252   // read on the main thread. Note that transactions on the cookie table may
  2253   // have occurred on the main thread since, making the background data stale.
  2254   for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
  2255     const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
  2257     // Tiebreak: if the given base domain has already been read in, ignore
  2258     // the background data. Note that readSet may contain domains that were
  2259     // queried but found not to be in the db -- that's harmless.
  2260     if (mDefaultDBState->readSet.GetEntry(tuple.key))
  2261       continue;
  2263     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
  2266   mDefaultDBState->stmtReadDomain = nullptr;
  2267   mDefaultDBState->pendingRead = nullptr;
  2268   mDefaultDBState->readListener = nullptr;
  2269   mDefaultDBState->syncConn = nullptr;
  2270   mDefaultDBState->hostArray.Clear();
  2271   mDefaultDBState->readSet.Clear();
  2273   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
  2274                                   mDefaultDBState->cookieCount));
  2276   mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
  2279 void
  2280 nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
  2282   // We may be in the private browsing DB state, with a pending read on the
  2283   // default DB state. (This would occur if we started up in private browsing
  2284   // mode.) As long as we do all our operations on the default state, we're OK.
  2285   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2286   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2287   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2289   // Cancel the pending read, kill the read listener, and empty the array
  2290   // of data already read in on the background thread.
  2291   mDefaultDBState->readListener->Cancel();
  2292   DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
  2293   NS_ASSERT_SUCCESS(rv);
  2295   mDefaultDBState->stmtReadDomain = nullptr;
  2296   mDefaultDBState->pendingRead = nullptr;
  2297   mDefaultDBState->readListener = nullptr;
  2298   mDefaultDBState->hostArray.Clear();
  2300   // Only clear the 'readSet' table if we no longer need to know what set of
  2301   // data is already accounted for.
  2302   if (aPurgeReadSet)
  2303     mDefaultDBState->readSet.Clear();
  2306 void
  2307 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
  2309   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2310     "not in default db state");
  2312   // Fast path 1: nothing to read, or we've already finished reading.
  2313   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2314     return;
  2316   // Fast path 2: already read in this particular domain.
  2317   if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
  2318     return;
  2320   // Read in the data synchronously.
  2321   // see IDX_NAME, etc. for parameter indexes
  2322   nsresult rv;
  2323   if (!mDefaultDBState->stmtReadDomain) {
  2324     // Cache the statement, since it's likely to be used again.
  2325     rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2326       "SELECT "
  2327         "name, "
  2328         "value, "
  2329         "host, "
  2330         "path, "
  2331         "expiry, "
  2332         "lastAccessed, "
  2333         "creationTime, "
  2334         "isSecure, "
  2335         "isHttpOnly "
  2336       "FROM moz_cookies "
  2337       "WHERE baseDomain = :baseDomain "
  2338       "  AND appId = :appId "
  2339       "  AND inBrowserElement = :inBrowserElement"),
  2340       getter_AddRefs(mDefaultDBState->stmtReadDomain));
  2342     if (NS_FAILED(rv)) {
  2343       // Recreate the database.
  2344       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2345         ("EnsureReadDomain(): corruption detected when creating statement "
  2346          "with rv 0x%x", rv));
  2347       HandleCorruptDB(mDefaultDBState);
  2348       return;
  2352   NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
  2354   mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
  2356   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
  2357     NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
  2358   NS_ASSERT_SUCCESS(rv);
  2359   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2360     NS_LITERAL_CSTRING("appId"), aKey.mAppId);
  2361   NS_ASSERT_SUCCESS(rv);
  2362   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2363     NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
  2364   NS_ASSERT_SUCCESS(rv);
  2367   bool hasResult;
  2368   nsCString name, value, host, path;
  2369   nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
  2370   while (1) {
  2371     rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
  2372     if (NS_FAILED(rv)) {
  2373       // Recreate the database.
  2374       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2375         ("EnsureReadDomain(): corruption detected when reading result "
  2376          "with rv 0x%x", rv));
  2377       HandleCorruptDB(mDefaultDBState);
  2378       return;
  2381     if (!hasResult)
  2382       break;
  2384     array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
  2387   // Add the cookies to the table in a single operation. This makes sure that
  2388   // either all the cookies get added, or in the case of corruption, none.
  2389   for (uint32_t i = 0; i < array.Length(); ++i) {
  2390     AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
  2393   // Add it to the hashset of read entries, so we don't read it again.
  2394   mDefaultDBState->readSet.PutEntry(aKey);
  2396   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2397     ("EnsureReadDomain(): %ld cookies read for base domain %s, "
  2398      " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
  2399      (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
  2402 void
  2403 nsCookieService::EnsureReadComplete()
  2405   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2406     "not in default db state");
  2408   // Fast path 1: nothing to read, or we've already finished reading.
  2409   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2410     return;
  2412   // Cancel the pending read, so we don't get any more results.
  2413   CancelAsyncRead(false);
  2415   // Read in the data synchronously.
  2416   // see IDX_NAME, etc. for parameter indexes
  2417   nsCOMPtr<mozIStorageStatement> stmt;
  2418   nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2419     "SELECT "
  2420       "name, "
  2421       "value, "
  2422       "host, "
  2423       "path, "
  2424       "expiry, "
  2425       "lastAccessed, "
  2426       "creationTime, "
  2427       "isSecure, "
  2428       "isHttpOnly, "
  2429       "baseDomain, "
  2430       "appId,  "
  2431       "inBrowserElement "
  2432     "FROM moz_cookies "
  2433     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
  2435   if (NS_FAILED(rv)) {
  2436     // Recreate the database.
  2437     COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2438       ("EnsureReadComplete(): corruption detected when creating statement "
  2439        "with rv 0x%x", rv));
  2440     HandleCorruptDB(mDefaultDBState);
  2441     return;
  2444   nsCString baseDomain, name, value, host, path;
  2445   uint32_t appId;
  2446   bool inBrowserElement, hasResult;
  2447   nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
  2448   while (1) {
  2449     rv = stmt->ExecuteStep(&hasResult);
  2450     if (NS_FAILED(rv)) {
  2451       // Recreate the database.
  2452       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2453         ("EnsureReadComplete(): corruption detected when reading result "
  2454          "with rv 0x%x", rv));
  2455       HandleCorruptDB(mDefaultDBState);
  2456       return;
  2459     if (!hasResult)
  2460       break;
  2462     // Make sure we haven't already read the data.
  2463     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
  2464     appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID));
  2465     inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM));
  2466     nsCookieKey key(baseDomain, appId, inBrowserElement);
  2467     if (mDefaultDBState->readSet.GetEntry(key))
  2468       continue;
  2470     CookieDomainTuple* tuple = array.AppendElement();
  2471     tuple->key = key;
  2472     tuple->cookie = GetCookieFromRow(stmt);
  2475   // Add the cookies to the table in a single operation. This makes sure that
  2476   // either all the cookies get added, or in the case of corruption, none.
  2477   for (uint32_t i = 0; i < array.Length(); ++i) {
  2478     CookieDomainTuple& tuple = array[i];
  2479     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
  2480       false);
  2483   mDefaultDBState->syncConn = nullptr;
  2484   mDefaultDBState->readSet.Clear();
  2486   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2487     ("EnsureReadComplete(): %ld cookies read", array.Length()));
  2490 NS_IMETHODIMP
  2491 nsCookieService::ImportCookies(nsIFile *aCookieFile)
  2493   if (!mDBState) {
  2494     NS_WARNING("No DBState! Profile already closed?");
  2495     return NS_ERROR_NOT_AVAILABLE;
  2498   // Make sure we're in the default DB state. We don't want people importing
  2499   // cookies into a private browsing session!
  2500   if (mDBState != mDefaultDBState) {
  2501     NS_WARNING("Trying to import cookies in a private browsing session!");
  2502     return NS_ERROR_NOT_AVAILABLE;
  2505   nsresult rv;
  2506   nsCOMPtr<nsIInputStream> fileInputStream;
  2507   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
  2508   if (NS_FAILED(rv)) return rv;
  2510   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
  2511   if (NS_FAILED(rv)) return rv;
  2513   // First, ensure we've read in everything from the database, if we have one.
  2514   EnsureReadComplete();
  2516   static const char kTrue[] = "TRUE";
  2518   nsAutoCString buffer, baseDomain;
  2519   bool isMore = true;
  2520   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
  2521   nsASingleFragmentCString::char_iterator iter;
  2522   int32_t numInts;
  2523   int64_t expires;
  2524   bool isDomain, isHttpOnly = false;
  2525   uint32_t originalCookieCount = mDefaultDBState->cookieCount;
  2527   int64_t currentTimeInUsec = PR_Now();
  2528   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2529   // we use lastAccessedCounter to keep cookies in recently-used order,
  2530   // so we start by initializing to currentTime (somewhat arbitrary)
  2531   int64_t lastAccessedCounter = currentTimeInUsec;
  2533   /* file format is:
  2535    * host \t isDomain \t path \t secure \t expires \t name \t cookie
  2537    * if this format isn't respected we move onto the next line in the file.
  2538    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
  2539    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
  2540    * expires is a int64_t integer
  2541    * note 1: cookie can contain tabs.
  2542    * note 2: cookies will be stored in order of lastAccessed time:
  2543    *         most-recently used come first; least-recently-used come last.
  2544    */
  2546   /*
  2547    * ...but due to bug 178933, we hide HttpOnly cookies from older code
  2548    * in a comment, so they don't expose HttpOnly cookies to JS.
  2550    * The format for HttpOnly cookies is
  2552    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
  2554    */
  2556   // We will likely be adding a bunch of cookies to the DB, so we use async
  2557   // batching with storage to make this super fast.
  2558   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2559   if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
  2560     mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2563   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
  2564     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
  2565       isHttpOnly = true;
  2566       hostIndex = sizeof(kHttpOnlyPrefix) - 1;
  2567     } else if (buffer.IsEmpty() || buffer.First() == '#') {
  2568       continue;
  2569     } else {
  2570       isHttpOnly = false;
  2571       hostIndex = 0;
  2574     // this is a cheap, cheesy way of parsing a tab-delimited line into
  2575     // string indexes, which can be lopped off into substrings. just for
  2576     // purposes of obfuscation, it also checks that each token was found.
  2577     // todo: use iterators?
  2578     if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
  2579         (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
  2580         (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
  2581         (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
  2582         (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
  2583         (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
  2584       continue;
  2587     // check the expirytime first - if it's expired, ignore
  2588     // nullstomp the trailing tab, to avoid copying the string
  2589     buffer.BeginWriting(iter);
  2590     *(iter += nameIndex - 1) = char(0);
  2591     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
  2592     if (numInts != 1 || expires < currentTime) {
  2593       continue;
  2596     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
  2597     const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
  2598     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
  2599     // and discard
  2600     if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
  2601         host.FindChar(':') != kNotFound) {
  2602       continue;
  2605     // compute the baseDomain from the host
  2606     rv = GetBaseDomainFromHost(host, baseDomain);
  2607     if (NS_FAILED(rv))
  2608       continue;
  2610     // pre-existing cookies have appId=0, inBrowser=false
  2611     nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  2613     // Create a new nsCookie and assign the data. We don't know the cookie
  2614     // creation time, so just use the current time to generate a unique one.
  2615     nsRefPtr<nsCookie> newCookie =
  2616       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
  2617                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
  2618                        host,
  2619                        baseDomain,
  2620                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
  2621                        expires,
  2622                        lastAccessedCounter,
  2623                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2624                        false,
  2625                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
  2626                        isHttpOnly);
  2627     if (!newCookie) {
  2628       return NS_ERROR_OUT_OF_MEMORY;
  2631     // trick: preserve the most-recently-used cookie ordering,
  2632     // by successively decrementing the lastAccessed time
  2633     lastAccessedCounter--;
  2635     if (originalCookieCount == 0) {
  2636       AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
  2638     else {
  2639       AddInternal(key, newCookie, currentTimeInUsec,
  2640                   nullptr, nullptr, true);
  2644   // If we need to write to disk, do so now.
  2645   if (paramsArray) {
  2646     uint32_t length;
  2647     paramsArray->GetLength(&length);
  2648     if (length) {
  2649       rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
  2650       NS_ASSERT_SUCCESS(rv);
  2651       nsCOMPtr<mozIStoragePendingStatement> handle;
  2652       rv = mDefaultDBState->stmtInsert->ExecuteAsync(
  2653         mDefaultDBState->insertListener, getter_AddRefs(handle));
  2654       NS_ASSERT_SUCCESS(rv);
  2659   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
  2660     mDefaultDBState->cookieCount));
  2662   return NS_OK;
  2665 /******************************************************************************
  2666  * nsCookieService impl:
  2667  * private GetCookie/SetCookie helpers
  2668  ******************************************************************************/
  2670 // helper function for GetCookieList
  2671 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
  2673 // Comparator class for sorting cookies before sending to a server.
  2674 class CompareCookiesForSending
  2676 public:
  2677   bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2679     return aCookie1->CreationTime() == aCookie2->CreationTime() &&
  2680            aCookie2->Path().Length() == aCookie1->Path().Length();
  2683   bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2685     // compare by cookie path length in accordance with RFC2109
  2686     int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
  2687     if (result != 0)
  2688       return result < 0;
  2690     // when path lengths match, older cookies should be listed first.  this is
  2691     // required for backwards compatibility since some websites erroneously
  2692     // depend on receiving cookies in the order in which they were sent to the
  2693     // browser!  see bug 236772.
  2694     return aCookie1->CreationTime() < aCookie2->CreationTime();
  2696 };
  2698 void
  2699 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
  2700                                          bool aIsForeign,
  2701                                          bool aHttpBound,
  2702                                          uint32_t aAppId,
  2703                                          bool aInBrowserElement,
  2704                                          bool aIsPrivate,
  2705                                          nsCString &aOrigDomain,
  2706                                          nsCString &aCookieString)
  2708   NS_ASSERTION(aHostURI, "null host!");
  2710   if (!mDBState) {
  2711     NS_WARNING("No DBState! Profile already closed?");
  2712     return;
  2715   AutoRestore<DBState*> savePrevDBState(mDBState);
  2716   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  2718   // get the base domain, host, and path from the URI.
  2719   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
  2720   // file:// URI's (i.e. with an empty host) are allowed, but any other
  2721   // scheme must have a non-empty host. A trailing dot in the host
  2722   // is acceptable.
  2723   bool requireHostMatch;
  2724   nsAutoCString baseDomain, hostFromURI, pathFromURI;
  2725   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  2726   if (NS_SUCCEEDED(rv))
  2727     rv = aHostURI->GetAsciiHost(hostFromURI);
  2728   if (NS_SUCCEEDED(rv))
  2729     rv = aHostURI->GetPath(pathFromURI);
  2730   if (NS_FAILED(rv)) {
  2731     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
  2732     return;
  2735   // check default prefs
  2736   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  2737                                          nullptr);
  2738   // for GetCookie(), we don't fire rejection notifications.
  2739   switch (cookieStatus) {
  2740   case STATUS_REJECTED:
  2741   case STATUS_REJECTED_WITH_ERROR:
  2742     return;
  2743   default:
  2744     break;
  2747   // check if aHostURI is using an https secure protocol.
  2748   // if it isn't, then we can't send a secure cookie over the connection.
  2749   // if SchemeIs fails, assume an insecure connection, to be on the safe side
  2750   bool isSecure;
  2751   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
  2752     isSecure = false;
  2755   nsCookie *cookie;
  2756   nsAutoTArray<nsCookie*, 8> foundCookieList;
  2757   int64_t currentTimeInUsec = PR_Now();
  2758   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2759   bool stale = false;
  2761   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  2762   EnsureReadDomain(key);
  2764   // perform the hash lookup
  2765   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  2766   if (!entry)
  2767     return;
  2769   // iterate the cookies!
  2770   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  2771   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  2772     cookie = cookies[i];
  2774     // Check the origin key. We only continue if the saved
  2775     // origin matches matches the origin domain and a populated
  2776     // 'aOrigDomain' indicates that first party isolation is active
  2777     // FIXME:MSvB, other places iterate cookies too, handle them likewise?
  2778     if (!aOrigDomain.IsEmpty() && cookie->Origin() != aOrigDomain) {
  2779       continue;
  2782     // check the host, since the base domain lookup is conservative.
  2783     // first, check for an exact host or domain cookie match, e.g. "google.com"
  2784     // or ".google.com"; second a subdomain match, e.g.
  2785     // host = "mail.google.com", cookie domain = ".google.com".
  2786     if (cookie->RawHost() != hostFromURI &&
  2787         !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
  2788       continue;
  2790     // if the cookie is secure and the host scheme isn't, we can't send it
  2791     if (cookie->IsSecure() && !isSecure)
  2792       continue;
  2794     // if the cookie is httpOnly and it's not going directly to the HTTP
  2795     // connection, don't send it
  2796     if (cookie->IsHttpOnly() && !aHttpBound)
  2797       continue;
  2799     // calculate cookie path length, excluding trailing '/'
  2800     uint32_t cookiePathLen = cookie->Path().Length();
  2801     if (cookiePathLen > 0 && cookie->Path().Last() == '/')
  2802       --cookiePathLen;
  2804     // if the nsIURI path is shorter than the cookie path, don't send it back
  2805     if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
  2806       continue;
  2808     if (pathFromURI.Length() > cookiePathLen &&
  2809         !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
  2810       /*
  2811        * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
  2812        * '/' is the "standard" case; the '?' test allows a site at host/abc?def
  2813        * to receive a cookie that has a path attribute of abc.  this seems
  2814        * strange but at least one major site (citibank, bug 156725) depends
  2815        * on it.  The test for # and ; are put in to proactively avoid problems
  2816        * with other sites - these are the only other chars allowed in the path.
  2817        */
  2818       continue;
  2821     // check if the cookie has expired
  2822     if (cookie->Expiry() <= currentTime) {
  2823       continue;
  2826     // all checks passed - add to list and check if lastAccessed stamp needs updating
  2827     foundCookieList.AppendElement(cookie);
  2828     if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2829       stale = true;
  2832   int32_t count = foundCookieList.Length();
  2833   if (count == 0)
  2834     return;
  2836   // update lastAccessed timestamps. we only do this if the timestamp is stale
  2837   // by a certain amount, to avoid thrashing the db during pageload.
  2838   if (stale) {
  2839     // Create an array of parameters to bind to our update statement. Batching
  2840     // is OK here since we're updating cookies with no interleaved operations.
  2841     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2842     mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
  2843     if (mDBState->dbConn) {
  2844       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2847     for (int32_t i = 0; i < count; ++i) {
  2848       cookie = foundCookieList.ElementAt(i);
  2850       if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2851         UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
  2853     // Update the database now if necessary.
  2854     if (paramsArray) {
  2855       uint32_t length;
  2856       paramsArray->GetLength(&length);
  2857       if (length) {
  2858         DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  2859         NS_ASSERT_SUCCESS(rv);
  2860         nsCOMPtr<mozIStoragePendingStatement> handle;
  2861         rv = stmt->ExecuteAsync(mDBState->updateListener,
  2862           getter_AddRefs(handle));
  2863         NS_ASSERT_SUCCESS(rv);
  2868   // return cookies in order of path length; longest to shortest.
  2869   // this is required per RFC2109.  if cookies match in length,
  2870   // then sort by creation time (see bug 236772).
  2871   foundCookieList.Sort(CompareCookiesForSending());
  2873   for (int32_t i = 0; i < count; ++i) {
  2874     cookie = foundCookieList.ElementAt(i);
  2876     // check if we have anything to write
  2877     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
  2878       // if we've already added a cookie to the return list, append a "; " so
  2879       // that subsequent cookies are delimited in the final list.
  2880       if (!aCookieString.IsEmpty()) {
  2881         aCookieString.AppendLiteral("; ");
  2884       if (!cookie->Name().IsEmpty()) {
  2885         // we have a name and value - write both
  2886         aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
  2887       } else {
  2888         // just write value
  2889         aCookieString += cookie->Value();
  2894   if (!aCookieString.IsEmpty())
  2895     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
  2898 // processes a single cookie, and returns true if there are more cookies
  2899 // to be processed
  2900 bool
  2901 nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
  2902                                    const nsCookieKey             &aKey,
  2903                                    bool                           aRequireHostMatch,
  2904                                    const nsCString               &aOrigin,
  2905                                    CookieStatus                   aStatus,
  2906                                    nsDependentCString            &aCookieHeader,
  2907                                    int64_t                        aServerTime,
  2908                                    bool                           aFromHttp,
  2909                                    nsIChannel                    *aChannel)
  2911   NS_ASSERTION(aHostURI, "null host!");
  2913   // create a stack-based nsCookieAttributes, to store all the
  2914   // attributes parsed from the cookie
  2915   nsCookieAttributes cookieAttributes;
  2917   // init expiryTime such that session cookies won't prematurely expire
  2918   cookieAttributes.expiryTime = INT64_MAX;
  2920   // aCookieHeader is an in/out param to point to the next cookie, if
  2921   // there is one. Save the present value for logging purposes
  2922   nsDependentCString savedCookieHeader(aCookieHeader);
  2924   // newCookie says whether there are multiple cookies in the header;
  2925   // so we can handle them separately.
  2926   bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
  2928   int64_t currentTimeInUsec = PR_Now();
  2930   // calculate expiry time of cookie.
  2931   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
  2932                                          currentTimeInUsec / PR_USEC_PER_SEC);
  2933   if (aStatus == STATUS_ACCEPT_SESSION) {
  2934     // force lifetime to session. note that the expiration time, if set above,
  2935     // will still apply.
  2936     cookieAttributes.isSession = true;
  2939   // reject cookie if it's over the size limit, per RFC2109
  2940   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
  2941     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
  2942     return newCookie;
  2945   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
  2946     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
  2947     return newCookie;
  2950   // domain & path checks
  2951   if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
  2952     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
  2953     return newCookie;
  2955   if (!CheckPath(cookieAttributes, aHostURI)) {
  2956     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
  2957     return newCookie;
  2960   // create a new nsCookie and copy attributes
  2961 //FIXME:MSvB, The name and value vars are neither host nor key
  2962 //FIXME:MSvB, host shows up in cookie inspector, as a index key
  2963   nsRefPtr<nsCookie> cookie =
  2964     nsCookie::Create(cookieAttributes.name,
  2965                      cookieAttributes.value,
  2966                      cookieAttributes.host,
  2967                      aOrigin,
  2968                      cookieAttributes.path,
  2969                      cookieAttributes.expiryTime,
  2970                      currentTimeInUsec,
  2971                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2972                      cookieAttributes.isSession,
  2973                      cookieAttributes.isSecure,
  2974                      cookieAttributes.isHttpOnly);
  2975   if (!cookie)
  2976     return newCookie;
  2978   // check permissions from site permission list, or ask the user,
  2979   // to determine if we can set the cookie
  2980   if (mPermissionService) {
  2981     bool permission;
  2982     mPermissionService->CanSetCookie(aHostURI,
  2983                                      aChannel,
  2984                                      static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
  2985                                      &cookieAttributes.isSession,
  2986                                      &cookieAttributes.expiryTime,
  2987                                      &permission);
  2988     if (!permission) {
  2989       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
  2990       NotifyRejected(aHostURI);
  2991       return newCookie;
  2994     // update isSession and expiry attributes, in case they changed
  2995     cookie->SetIsSession(cookieAttributes.isSession);
  2996     cookie->SetExpiry(cookieAttributes.expiryTime);
  2999   // add the cookie to the list. AddInternal() takes care of logging.
  3000   // we get the current time again here, since it may have changed during prompting
  3001   AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
  3002               aFromHttp);
  3003   return newCookie;
  3006 // this is a backend function for adding a cookie to the list, via SetCookie.
  3007 // also used in the cookie manager, for profile migration from IE.
  3008 // it either replaces an existing cookie; or adds the cookie to the hashtable,
  3009 // and deletes a cookie (if maximum number of cookies has been
  3010 // reached). also performs list maintenance by removing expired cookies.
  3011 void
  3012 nsCookieService::AddInternal(const nsCookieKey             &aKey,
  3013                              nsCookie                      *aCookie,
  3014                              int64_t                        aCurrentTimeInUsec,
  3015                              nsIURI                        *aHostURI,
  3016                              const char                    *aCookieHeader,
  3017                              bool                           aFromHttp)
  3019   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
  3021   // if the new cookie is httponly, make sure we're not coming from script
  3022   if (!aFromHttp && aCookie->IsHttpOnly()) {
  3023     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3024       "cookie is httponly; coming from script");
  3025     return;
  3028   nsListIter matchIter;
  3029   bool foundCookie = FindCookie(aKey, aCookie->Origin(),
  3030     aCookie->Host(), aCookie->Name(), aCookie->Path(), matchIter);
  3032   nsRefPtr<nsCookie> oldCookie;
  3033   nsCOMPtr<nsIArray> purgedList;
  3034   if (foundCookie) {
  3035     oldCookie = matchIter.Cookie();
  3037     // Check if the old cookie is stale (i.e. has already expired). If so, we
  3038     // need to be careful about the semantics of removing it and adding the new
  3039     // cookie: we want the behavior wrt adding the new cookie to be the same as
  3040     // if it didn't exist, but we still want to fire a removal notification.
  3041     if (oldCookie->Expiry() <= currentTime) {
  3042       if (aCookie->Expiry() <= currentTime) {
  3043         // The new cookie has expired and the old one is stale. Nothing to do.
  3044         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3045           "cookie has already expired");
  3046         return;
  3049       // Remove the stale cookie. We save notification for later, once all list
  3050       // modifications are complete.
  3051       RemoveCookieFromList(matchIter);
  3052       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3053         "stale cookie was purged");
  3054       purgedList = CreatePurgeList(oldCookie);
  3056       // We've done all we need to wrt removing and notifying the stale cookie.
  3057       // From here on out, we pretend pretend it didn't exist, so that we
  3058       // preserve expected notification semantics when adding the new cookie.
  3059       foundCookie = false;
  3061     } else {
  3062       // If the old cookie is httponly, make sure we're not coming from script.
  3063       if (!aFromHttp && oldCookie->IsHttpOnly()) {
  3064         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3065           "previously stored cookie is httponly; coming from script");
  3066         return;
  3069       // Remove the old cookie.
  3070       RemoveCookieFromList(matchIter);
  3072       // If the new cookie has expired -- i.e. the intent was simply to delete
  3073       // the old cookie -- then we're done.
  3074       if (aCookie->Expiry() <= currentTime) {
  3075         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3076           "previously stored cookie was deleted");
  3077         NotifyChanged(oldCookie, MOZ_UTF16("deleted"));
  3078         return;
  3081       // Preserve creation time of cookie for ordering purposes.
  3082       aCookie->SetCreationTime(oldCookie->CreationTime());
  3085   } else {
  3086     // check if cookie has already expired
  3087     if (aCookie->Expiry() <= currentTime) {
  3088       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3089         "cookie has already expired");
  3090       return;
  3093     // check if we have to delete an old cookie.
  3094     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  3095     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
  3096       nsListIter iter;
  3097       FindStaleCookie(entry, currentTime, iter);
  3098       oldCookie = iter.Cookie();
  3100       // remove the oldest cookie from the domain
  3101       RemoveCookieFromList(iter);
  3102       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
  3103       purgedList = CreatePurgeList(oldCookie);
  3105     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
  3106       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
  3107       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
  3108       if (maxAge >= purgeAge) {
  3109         // we're over both size and age limits by 10%; time to purge the table!
  3110         // do this by:
  3111         // 1) removing expired cookies;
  3112         // 2) evicting the balance of old cookies until we reach the size limit.
  3113         // note that the cookieOldestTime indicator can be pessimistic - if it's
  3114         // older than the actual oldest cookie, we'll just purge more eagerly.
  3115         purgedList = PurgeCookies(aCurrentTimeInUsec);
  3120   // Add the cookie to the db. We do not supply a params array for batching
  3121   // because this might result in removals and additions being out of order.
  3122   AddCookieToList(aKey, aCookie, mDBState, nullptr);
  3123   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
  3125   // Now that list mutations are complete, notify observers. We do it here
  3126   // because observers may themselves attempt to mutate the list.
  3127   if (purgedList) {
  3128     NotifyChanged(purgedList, MOZ_UTF16("batch-deleted"));
  3131   NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
  3132                                      : MOZ_UTF16("added"));
  3135 /******************************************************************************
  3136  * nsCookieService impl:
  3137  * private cookie header parsing functions
  3138  ******************************************************************************/
  3140 // The following comment block elucidates the function of ParseAttributes.
  3141 /******************************************************************************
  3142  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
  3143  ** please note: this BNF deviates from both specifications, and reflects this
  3144  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
  3146  ** Differences from RFC2109/2616 and explanations:
  3147     1. implied *LWS
  3148          The grammar described by this specification is word-based. Except
  3149          where noted otherwise, linear white space (<LWS>) can be included
  3150          between any two adjacent words (token or quoted-string), and
  3151          between adjacent words and separators, without changing the
  3152          interpretation of a field.
  3153        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
  3155     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
  3156        common use inside values.
  3158     3. tokens and values have looser restrictions on allowed characters than
  3159        spec. This is also due to certain characters being in common use inside
  3160        values. We allow only '=' to separate token/value pairs, and ';' to
  3161        terminate tokens or values. <LWS> is allowed within tokens and values
  3162        (see bug 206022).
  3164     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
  3165        reject control chars or non-ASCII chars. This is erring on the loose
  3166        side, since there's probably no good reason to enforce this strictness.
  3168     5. cookie <NAME> is optional, where spec requires it. This is a fairly
  3169        trivial case, but allows the flexibility of setting only a cookie <VALUE>
  3170        with a blank <NAME> and is required by some sites (see bug 169091).
  3172     6. Attribute "HttpOnly", not covered in the RFCs, is supported
  3173        (see bug 178993).
  3175  ** Begin BNF:
  3176     token         = 1*<any allowed-chars except separators>
  3177     value         = 1*<any allowed-chars except value-sep>
  3178     separators    = ";" | "="
  3179     value-sep     = ";"
  3180     cookie-sep    = CR | LF
  3181     allowed-chars = <any OCTET except NUL or cookie-sep>
  3182     OCTET         = <any 8-bit sequence of data>
  3183     LWS           = SP | HT
  3184     NUL           = <US-ASCII NUL, null control character (0)>
  3185     CR            = <US-ASCII CR, carriage return (13)>
  3186     LF            = <US-ASCII LF, linefeed (10)>
  3187     SP            = <US-ASCII SP, space (32)>
  3188     HT            = <US-ASCII HT, horizontal-tab (9)>
  3190     set-cookie    = "Set-Cookie:" cookies
  3191     cookies       = cookie *( cookie-sep cookie )
  3192     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
  3193     NAME          = token                                ; cookie name
  3194     VALUE         = value                                ; cookie value
  3195     cookie-av     = token ["=" value]
  3197     valid values for cookie-av (checked post-parsing) are:
  3198     cookie-av     = "Path"    "=" value
  3199                   | "Domain"  "=" value
  3200                   | "Expires" "=" value
  3201                   | "Max-Age" "=" value
  3202                   | "Comment" "=" value
  3203                   | "Version" "=" value
  3204                   | "Secure"
  3205                   | "HttpOnly"
  3207 ******************************************************************************/
  3209 // helper functions for GetTokenValue
  3210 static inline bool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
  3211 static inline bool isterminator     (char c) { return c == '\n' || c == '\r'; }
  3212 static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
  3213 static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
  3215 // Parse a single token/value pair.
  3216 // Returns true if a cookie terminator is found, so caller can parse new cookie.
  3217 bool
  3218 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
  3219                                nsASingleFragmentCString::const_char_iterator &aEndIter,
  3220                                nsDependentCSubstring                         &aTokenString,
  3221                                nsDependentCSubstring                         &aTokenValue,
  3222                                bool                                          &aEqualsFound)
  3224   nsASingleFragmentCString::const_char_iterator start, lastSpace;
  3225   // initialize value string to clear garbage
  3226   aTokenValue.Rebind(aIter, aIter);
  3228   // find <token>, including any <LWS> between the end-of-token and the
  3229   // token separator. we'll remove trailing <LWS> next
  3230   while (aIter != aEndIter && iswhitespace(*aIter))
  3231     ++aIter;
  3232   start = aIter;
  3233   while (aIter != aEndIter && !istokenseparator(*aIter))
  3234     ++aIter;
  3236   // remove trailing <LWS>; first check we're not at the beginning
  3237   lastSpace = aIter;
  3238   if (lastSpace != start) {
  3239     while (--lastSpace != start && iswhitespace(*lastSpace))
  3240       continue;
  3241     ++lastSpace;
  3243   aTokenString.Rebind(start, lastSpace);
  3245   aEqualsFound = (*aIter == '=');
  3246   if (aEqualsFound) {
  3247     // find <value>
  3248     while (++aIter != aEndIter && iswhitespace(*aIter))
  3249       continue;
  3251     start = aIter;
  3253     // process <token>
  3254     // just look for ';' to terminate ('=' allowed)
  3255     while (aIter != aEndIter && !isvalueseparator(*aIter))
  3256       ++aIter;
  3258     // remove trailing <LWS>; first check we're not at the beginning
  3259     if (aIter != start) {
  3260       lastSpace = aIter;
  3261       while (--lastSpace != start && iswhitespace(*lastSpace))
  3262         continue;
  3263       aTokenValue.Rebind(start, ++lastSpace);
  3267   // aIter is on ';', or terminator, or EOS
  3268   if (aIter != aEndIter) {
  3269     // if on terminator, increment past & return true to process new cookie
  3270     if (isterminator(*aIter)) {
  3271       ++aIter;
  3272       return true;
  3274     // fall-through: aIter is on ';', increment and return false
  3275     ++aIter;
  3277   return false;
  3280 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
  3281 // cookie struct here, because we don't know which one to use until we've parsed the header.
  3282 bool
  3283 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
  3284                                  nsCookieAttributes &aCookieAttributes)
  3286   static const char kPath[]    = "path";
  3287   static const char kDomain[]  = "domain";
  3288   static const char kExpires[] = "expires";
  3289   static const char kMaxage[]  = "max-age";
  3290   static const char kSecure[]  = "secure";
  3291   static const char kHttpOnly[]  = "httponly";
  3293   nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
  3294   nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
  3295   aCookieHeader.BeginReading(cookieStart);
  3296   aCookieHeader.EndReading(cookieEnd);
  3298   aCookieAttributes.isSecure = false;
  3299   aCookieAttributes.isHttpOnly = false;
  3301   nsDependentCSubstring tokenString(cookieStart, cookieStart);
  3302   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
  3303   bool newCookie, equalsFound;
  3305   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
  3306   // if we find multiple cookies, return for processing
  3307   // note: if there's no '=', we assume token is <VALUE>. this is required by
  3308   //       some sites (see bug 169091).
  3309   // XXX fix the parser to parse according to <VALUE> grammar for this case
  3310   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3311   if (equalsFound) {
  3312     aCookieAttributes.name = tokenString;
  3313     aCookieAttributes.value = tokenValue;
  3314   } else {
  3315     aCookieAttributes.value = tokenString;
  3318   // extract remaining attributes
  3319   while (cookieStart != cookieEnd && !newCookie) {
  3320     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3322     if (!tokenValue.IsEmpty()) {
  3323       tokenValue.BeginReading(tempBegin);
  3324       tokenValue.EndReading(tempEnd);
  3327     // decide which attribute we have, and copy the string
  3328     if (tokenString.LowerCaseEqualsLiteral(kPath))
  3329       aCookieAttributes.path = tokenValue;
  3331     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
  3332       aCookieAttributes.host = tokenValue;
  3334     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
  3335       aCookieAttributes.expires = tokenValue;
  3337     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
  3338       aCookieAttributes.maxage = tokenValue;
  3340     // ignore any tokenValue for isSecure; just set the boolean
  3341     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
  3342       aCookieAttributes.isSecure = true;
  3344     // ignore any tokenValue for isHttpOnly (see bug 178993);
  3345     // just set the boolean
  3346     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
  3347       aCookieAttributes.isHttpOnly = true;
  3350   // rebind aCookieHeader, in case we need to process another cookie
  3351   aCookieHeader.Rebind(cookieStart, cookieEnd);
  3352   return newCookie;
  3355 /******************************************************************************
  3356  * nsCookieService impl:
  3357  * private domain & permission compliance enforcement functions
  3358  ******************************************************************************/
  3360 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
  3361 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
  3362 // dot may be present. If aHostURI is an IP address, an alias such as
  3363 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
  3364 // be the exact host, and aRequireHostMatch will be true to indicate that
  3365 // substring matches should not be performed.
  3366 nsresult
  3367 nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
  3368                                nsCString &aBaseDomain,
  3369                                bool      &aRequireHostMatch)
  3371   // get the base domain. this will fail if the host contains a leading dot,
  3372   // more than one trailing dot, or is otherwise malformed.
  3373   nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
  3374   aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3375                       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
  3376   if (aRequireHostMatch) {
  3377     // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
  3378     // such as 'co.uk', or the empty string. use the host as a key in such
  3379     // cases.
  3380     rv = aHostURI->GetAsciiHost(aBaseDomain);
  3382   NS_ENSURE_SUCCESS(rv, rv);
  3384   // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
  3385   if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
  3386     return NS_ERROR_INVALID_ARG;
  3388   // block any URIs without a host that aren't file:// URIs.
  3389   if (aBaseDomain.IsEmpty()) {
  3390     bool isFileURI = false;
  3391     aHostURI->SchemeIs("file", &isFileURI);
  3392     if (!isFileURI)
  3393       return NS_ERROR_INVALID_ARG;
  3396   return NS_OK;
  3399 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
  3400 // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
  3401 // that aHost is already normalized, and it may contain a leading dot
  3402 // (indicating that it represents a domain). A trailing dot may be present.
  3403 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
  3404 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
  3405 // leading dot will be treated as an error.
  3406 nsresult
  3407 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
  3408                                        nsCString        &aBaseDomain)
  3410   // aHost must not be the string '.'.
  3411   if (aHost.Length() == 1 && aHost.Last() == '.')
  3412     return NS_ERROR_INVALID_ARG;
  3414   // aHost may contain a leading dot; if so, strip it now.
  3415   bool domain = !aHost.IsEmpty() && aHost.First() == '.';
  3417   // get the base domain. this will fail if the host contains a leading dot,
  3418   // more than one trailing dot, or is otherwise malformed.
  3419   nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
  3420   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3421       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
  3422     // aHost is either an IP address, an alias such as 'localhost', an eTLD
  3423     // such as 'co.uk', or the empty string. use the host as a key in such
  3424     // cases; however, we reject any such hosts with a leading dot, since it
  3425     // doesn't make sense for them to be domain cookies.
  3426     if (domain)
  3427       return NS_ERROR_INVALID_ARG;
  3429     aBaseDomain = aHost;
  3430     return NS_OK;
  3432   return rv;
  3435 // Normalizes the given hostname, component by component. ASCII/ACE
  3436 // components are lower-cased, and UTF-8 components are normalized per
  3437 // RFC 3454 and converted to ACE.
  3438 nsresult
  3439 nsCookieService::NormalizeHost(nsCString &aHost)
  3441   if (!IsASCII(aHost)) {
  3442     nsAutoCString host;
  3443     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
  3444     if (NS_FAILED(rv))
  3445       return rv;
  3447     aHost = host;
  3450   ToLowerCase(aHost);
  3451   return NS_OK;
  3454 // returns true if 'a' is equal to or a subdomain of 'b',
  3455 // assuming no leading dots are present.
  3456 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
  3458   if (a == b)
  3459     return true;
  3460   if (a.Length() > b.Length())
  3461     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
  3462   return false;
  3465 CookieStatus
  3466 nsCookieService::CheckPrefs(nsIURI          *aHostURI,
  3467                             bool             aIsForeign,
  3468                             bool             aRequireHostMatch,
  3469                             const char      *aCookieHeader)
  3471   nsresult rv;
  3473   // don't let ftp sites get/set cookies (could be a security issue)
  3474   bool ftp;
  3475   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
  3476     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
  3477     return STATUS_REJECTED_WITH_ERROR;
  3480   // check the permission list first; if we find an entry, it overrides
  3481   // default prefs. see bug 184059.
  3482   if (mPermissionService) {
  3483     nsCookieAccess access;
  3484     // Not passing an nsIChannel here is probably OK; our implementation
  3485     // doesn't do anything with it anyway.
  3486     rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
  3488     // if we found an entry, use it
  3489     if (NS_SUCCEEDED(rv)) {
  3490       switch (access) {
  3491       case nsICookiePermission::ACCESS_DENY:
  3492         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3493                           aCookieHeader, "cookies are blocked for this site");
  3494         return STATUS_REJECTED;
  3496       case nsICookiePermission::ACCESS_ALLOW:
  3497         return STATUS_ACCEPTED;
  3499       case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
  3500         if (aIsForeign) {
  3501           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3502                             aCookieHeader, "third party cookies are blocked "
  3503                             "for this site");
  3504           return STATUS_REJECTED;
  3507         return STATUS_ACCEPTED;
  3509       case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
  3510         if (!aIsForeign)
  3511           return STATUS_ACCEPTED;
  3512         uint32_t priorCookieCount = 0;
  3513         nsAutoCString hostFromURI;
  3514         aHostURI->GetHost(hostFromURI);
  3515         CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3516         if (priorCookieCount == 0) {
  3517           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3518                             aCookieHeader, "third party cookies are blocked "
  3519                             "for this site");
  3520           return STATUS_REJECTED;
  3522         return STATUS_ACCEPTED;
  3527   // check default prefs
  3528   if (mCookieBehavior == BEHAVIOR_REJECT) {
  3529     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
  3530     return STATUS_REJECTED;
  3533   // check if cookie is foreign
  3534   if (aIsForeign) {
  3535     if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
  3536       return STATUS_ACCEPT_SESSION;
  3538     if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
  3539       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3540       return STATUS_REJECTED;
  3543     if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) {
  3544       uint32_t priorCookieCount = 0;
  3545       nsAutoCString hostFromURI;
  3546       aHostURI->GetHost(hostFromURI);
  3547       CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3548       if (priorCookieCount == 0) {
  3549         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3550         return STATUS_REJECTED;
  3552       if (mThirdPartySession)
  3553         return STATUS_ACCEPT_SESSION;
  3557   // if nothing has complained, accept cookie
  3558   return STATUS_ACCEPTED;
  3561 // processes domain attribute, and returns true if host has permission to set for this domain.
  3562 bool
  3563 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
  3564                              nsIURI             *aHostURI,
  3565                              const nsCString    &aBaseDomain,
  3566                              bool                aRequireHostMatch)
  3568   // get host from aHostURI
  3569   nsAutoCString hostFromURI;
  3570   aHostURI->GetAsciiHost(hostFromURI);
  3572   // if a domain is given, check the host has permission
  3573   if (!aCookieAttributes.host.IsEmpty()) {
  3574     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
  3575     if (aCookieAttributes.host.Length() > 1 &&
  3576         aCookieAttributes.host.First() == '.') {
  3577       aCookieAttributes.host.Cut(0, 1);
  3580     // switch to lowercase now, to avoid case-insensitive compares everywhere
  3581     ToLowerCase(aCookieAttributes.host);
  3583     // check whether the host is either an IP address, an alias such as
  3584     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
  3585     // cases, require an exact string match for the domain, and leave the cookie
  3586     // as a non-domain one. bug 105917 originally noted the requirement to deal
  3587     // with IP addresses.
  3588     if (aRequireHostMatch)
  3589       return hostFromURI.Equals(aCookieAttributes.host);
  3591     // ensure the proposed domain is derived from the base domain; and also
  3592     // that the host domain is derived from the proposed domain (per RFC2109).
  3593     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
  3594         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
  3595       // prepend a dot to indicate a domain cookie
  3596       aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
  3597       return true;
  3600     /*
  3601      * note: RFC2109 section 4.3.2 requires that we check the following:
  3602      * that the portion of host not in domain does not contain a dot.
  3603      * this prevents hosts of the form x.y.co.nz from setting cookies in the
  3604      * entire .co.nz domain. however, it's only a only a partial solution and
  3605      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
  3606      */
  3607     return false;
  3610   // no domain specified, use hostFromURI
  3611   aCookieAttributes.host = hostFromURI;
  3612   return true;
  3615 bool
  3616 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
  3617                            nsIURI             *aHostURI)
  3619   // if a path is given, check the host has permission
  3620   if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
  3621     // strip down everything after the last slash to get the path,
  3622     // ignoring slashes in the query string part.
  3623     // if we can QI to nsIURL, that'll take care of the query string portion.
  3624     // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
  3625     nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
  3626     if (hostURL) {
  3627       hostURL->GetDirectory(aCookieAttributes.path);
  3628     } else {
  3629       aHostURI->GetPath(aCookieAttributes.path);
  3630       int32_t slash = aCookieAttributes.path.RFindChar('/');
  3631       if (slash != kNotFound) {
  3632         aCookieAttributes.path.Truncate(slash + 1);
  3636 #if 0
  3637   } else {
  3638     /**
  3639      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
  3640      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
  3641      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
  3642      * been disabled, unless we can evangelize these sites.
  3643      */
  3644     // get path from aHostURI
  3645     nsAutoCString pathFromURI;
  3646     if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
  3647         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
  3648       return false;
  3650 #endif
  3653   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
  3654       aCookieAttributes.path.FindChar('\t') != kNotFound )
  3655     return false;
  3657   return true;
  3660 bool
  3661 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
  3662                            int64_t             aServerTime,
  3663                            int64_t             aCurrentTime)
  3665   /* Determine when the cookie should expire. This is done by taking the difference between 
  3666    * the server time and the time the server wants the cookie to expire, and adding that 
  3667    * difference to the client time. This localizes the client time regardless of whether or
  3668    * not the TZ environment variable was set on the client.
  3670    * Note: We need to consider accounting for network lag here, per RFC.
  3671    */
  3672   int64_t delta;
  3674   // check for max-age attribute first; this overrides expires attribute
  3675   if (!aCookieAttributes.maxage.IsEmpty()) {
  3676     // obtain numeric value of maxageAttribute
  3677     int64_t maxage;
  3678     int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
  3680     // default to session cookie if the conversion failed
  3681     if (numInts != 1) {
  3682       return true;
  3685     delta = maxage;
  3687   // check for expires attribute
  3688   } else if (!aCookieAttributes.expires.IsEmpty()) {
  3689     PRTime expires;
  3691     // parse expiry time
  3692     if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
  3693       return true;
  3696     delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
  3698   // default to session cookie if no attributes found
  3699   } else {
  3700     return true;
  3703   // if this addition overflows, expiryTime will be less than currentTime
  3704   // and the cookie will be expired - that's okay.
  3705   aCookieAttributes.expiryTime = aCurrentTime + delta;
  3707   return false;
  3710 /******************************************************************************
  3711  * nsCookieService impl:
  3712  * private cookielist management functions
  3713  ******************************************************************************/
  3715 void
  3716 nsCookieService::RemoveAllFromMemory()
  3718   // clearing the hashtable will call each nsCookieEntry's dtor,
  3719   // which releases all their respective children.
  3720   mDBState->hostTable.Clear();
  3721   mDBState->cookieCount = 0;
  3722   mDBState->cookieOldestTime = INT64_MAX;
  3725 // stores temporary data for enumerating over the hash entries,
  3726 // since enumeration is done using callback functions
  3727 struct nsPurgeData
  3729   typedef nsTArray<nsListIter> ArrayType;
  3731   nsPurgeData(int64_t aCurrentTime,
  3732               int64_t aPurgeTime,
  3733               ArrayType &aPurgeList,
  3734               nsIMutableArray *aRemovedList,
  3735               mozIStorageBindingParamsArray *aParamsArray)
  3736    : currentTime(aCurrentTime)
  3737    , purgeTime(aPurgeTime)
  3738    , oldestTime(INT64_MAX)
  3739    , purgeList(aPurgeList)
  3740    , removedList(aRemovedList)
  3741    , paramsArray(aParamsArray)
  3745   // the current time, in seconds
  3746   int64_t currentTime;
  3748   // lastAccessed time older than which cookies are eligible for purge
  3749   int64_t purgeTime;
  3751   // lastAccessed time of the oldest cookie found during purge, to update our indicator
  3752   int64_t oldestTime;
  3754   // list of cookies over the age limit, for purging
  3755   ArrayType &purgeList;
  3757   // list of all cookies we've removed, for notification
  3758   nsIMutableArray *removedList;
  3760   // The array of parameters to be bound to the statement for deletion later.
  3761   mozIStorageBindingParamsArray *paramsArray;
  3762 };
  3764 // comparator class for lastaccessed times of cookies.
  3765 class CompareCookiesByAge {
  3766 public:
  3767   bool Equals(const nsListIter &a, const nsListIter &b) const
  3769     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
  3770            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
  3773   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3775     // compare by lastAccessed time, and tiebreak by creationTime.
  3776     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
  3777     if (result != 0)
  3778       return result < 0;
  3780     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
  3782 };
  3784 // comparator class for sorting cookies by entry and index.
  3785 class CompareCookiesByIndex {
  3786 public:
  3787   bool Equals(const nsListIter &a, const nsListIter &b) const
  3789     NS_ASSERTION(a.entry != b.entry || a.index != b.index,
  3790       "cookie indexes should never be equal");
  3791     return false;
  3794   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3796     // compare by entryclass pointer, then by index.
  3797     if (a.entry != b.entry)
  3798       return a.entry < b.entry;
  3800     return a.index < b.index;
  3802 };
  3804 PLDHashOperator
  3805 purgeCookiesCallback(nsCookieEntry *aEntry,
  3806                      void          *aArg)
  3808   nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
  3810   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3811   mozIStorageBindingParamsArray *array = data.paramsArray;
  3812   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
  3813     nsListIter iter(aEntry, i);
  3814     nsCookie *cookie = cookies[i];
  3816     // check if the cookie has expired
  3817     if (cookie->Expiry() <= data.currentTime) {
  3818       data.removedList->AppendElement(cookie, false);
  3819       COOKIE_LOGEVICTED(cookie, "Cookie expired");
  3821       // remove from list; do not increment our iterator
  3822       gCookieService->RemoveCookieFromList(iter, array);
  3824     } else {
  3825       // check if the cookie is over the age limit
  3826       if (cookie->LastAccessed() <= data.purgeTime) {
  3827         data.purgeList.AppendElement(iter);
  3829       } else if (cookie->LastAccessed() < data.oldestTime) {
  3830         // reset our indicator
  3831         data.oldestTime = cookie->LastAccessed();
  3834       ++i;
  3837   return PL_DHASH_NEXT;
  3840 // purges expired and old cookies in a batch operation.
  3841 already_AddRefed<nsIArray>
  3842 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
  3844   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
  3845   EnsureReadComplete();
  3847 #ifdef PR_LOGGING
  3848   uint32_t initialCookieCount = mDBState->cookieCount;
  3849   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3850     ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
  3851      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3852 #endif
  3854   nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
  3856   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
  3858   // Create a params array to batch the removals. This is OK here because
  3859   // all the removals are in order, and there are no interleaved additions.
  3860   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  3861   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  3862   if (mDBState->dbConn) {
  3863     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  3866   nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
  3867     aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
  3868   mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
  3870 #ifdef PR_LOGGING
  3871   uint32_t postExpiryCookieCount = mDBState->cookieCount;
  3872 #endif
  3874   // now we have a list of iterators for cookies over the age limit.
  3875   // sort them by age, and then we'll see how many to remove...
  3876   purgeList.Sort(CompareCookiesByAge());
  3878   // only remove old cookies until we reach the max cookie limit, no more.
  3879   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
  3880     mDBState->cookieCount - mMaxNumberOfCookies : 0;
  3881   if (purgeList.Length() > excess) {
  3882     // We're not purging everything in the list, so update our indicator.
  3883     data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
  3885     purgeList.SetLength(excess);
  3888   // sort the list again, this time grouping cookies with a common entryclass
  3889   // together, and with ascending index. this allows us to iterate backwards
  3890   // over the list removing cookies, without having to adjust indexes as we go.
  3891   purgeList.Sort(CompareCookiesByIndex());
  3892   for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
  3893     nsCookie *cookie = purgeList[i].Cookie();
  3894     removedList->AppendElement(cookie, false);
  3895     COOKIE_LOGEVICTED(cookie, "Cookie too old");
  3897     RemoveCookieFromList(purgeList[i], paramsArray);
  3900   // Update the database if we have entries to purge.
  3901   if (paramsArray) {
  3902     uint32_t length;
  3903     paramsArray->GetLength(&length);
  3904     if (length) {
  3905       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  3906       NS_ASSERT_SUCCESS(rv);
  3907       nsCOMPtr<mozIStoragePendingStatement> handle;
  3908       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  3909       NS_ASSERT_SUCCESS(rv);
  3913   // reset the oldest time indicator
  3914   mDBState->cookieOldestTime = data.oldestTime;
  3916   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3917     ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
  3918      initialCookieCount - postExpiryCookieCount,
  3919      postExpiryCookieCount - mDBState->cookieCount,
  3920      mDBState->cookieCount,
  3921      aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3923   return removedList.forget();
  3926 // find whether a given cookie has been previously set. this is provided by the
  3927 // nsICookieManager2 interface.
  3928 NS_IMETHODIMP
  3929 nsCookieService::CookieExists(nsICookie2 *aCookie,
  3930                               bool       *aFoundCookie)
  3932   NS_ENSURE_ARG_POINTER(aCookie);
  3934   if (!mDBState) {
  3935     NS_WARNING("No DBState! Profile already closed?");
  3936     return NS_ERROR_NOT_AVAILABLE;
  3939   nsAutoCString host, origin, name, path;
  3940   nsresult rv = aCookie->GetHost(host);
  3941   NS_ENSURE_SUCCESS(rv, rv);
  3942   rv = aCookie->GetOrigin(origin);
  3943   NS_ENSURE_SUCCESS(rv, rv);
  3944   rv = aCookie->GetName(name);
  3945   NS_ENSURE_SUCCESS(rv, rv);
  3946   rv = aCookie->GetPath(path);
  3947   NS_ENSURE_SUCCESS(rv, rv);
  3949   nsAutoCString baseDomain;
  3950   rv = GetBaseDomainFromHost(host, baseDomain);
  3951   NS_ENSURE_SUCCESS(rv, rv);
  3953   nsListIter iter;
  3954   *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), origin, host, name, path, iter);
  3955   return NS_OK;
  3958 // For a given base domain, find either an expired cookie or the oldest cookie
  3959 // by lastAccessed time.
  3960 void
  3961 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
  3962                                  int64_t aCurrentTime,
  3963                                  nsListIter &aIter)
  3965   aIter.entry = nullptr;
  3967   int64_t oldestTime = 0;
  3968   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3969   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  3970     nsCookie *cookie = cookies[i];
  3972     // If we found an expired cookie, we're done.
  3973     if (cookie->Expiry() <= aCurrentTime) {
  3974       aIter.entry = aEntry;
  3975       aIter.index = i;
  3976       return;
  3979     // Check if we've found the oldest cookie so far.
  3980     if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
  3981       oldestTime = cookie->LastAccessed();
  3982       aIter.entry = aEntry;
  3983       aIter.index = i;
  3988 // count the number of cookies stored by a particular host. this is provided by the
  3989 // nsICookieManager2 interface.
  3990 NS_IMETHODIMP
  3991 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
  3992                                       uint32_t         *aCountFromHost)
  3994   if (!mDBState) {
  3995     NS_WARNING("No DBState! Profile already closed?");
  3996     return NS_ERROR_NOT_AVAILABLE;
  3999   // first, normalize the hostname, and fail if it contains illegal characters.
  4000   nsAutoCString host(aHost);
  4001   nsresult rv = NormalizeHost(host);
  4002   NS_ENSURE_SUCCESS(rv, rv);
  4004   nsAutoCString baseDomain;
  4005   rv = GetBaseDomainFromHost(host, baseDomain);
  4006   NS_ENSURE_SUCCESS(rv, rv);
  4008   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  4009   EnsureReadDomain(key);
  4011   // Return a count of all cookies, including expired.
  4012   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  4013   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
  4014   return NS_OK;
  4017 // get an enumerator of cookies stored by a particular host. this is provided by the
  4018 // nsICookieManager2 interface.
  4019 NS_IMETHODIMP
  4020 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
  4021                                     nsISimpleEnumerator **aEnumerator)
  4023   if (!mDBState) {
  4024     NS_WARNING("No DBState! Profile already closed?");
  4025     return NS_ERROR_NOT_AVAILABLE;
  4028   // first, normalize the hostname, and fail if it contains illegal characters.
  4029   nsAutoCString host(aHost);
  4030   nsresult rv = NormalizeHost(host);
  4031   NS_ENSURE_SUCCESS(rv, rv);
  4033   nsAutoCString baseDomain;
  4034   rv = GetBaseDomainFromHost(host, baseDomain);
  4035   NS_ENSURE_SUCCESS(rv, rv);
  4037   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  4038   EnsureReadDomain(key);
  4040   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  4041   if (!entry)
  4042     return NS_NewEmptyEnumerator(aEnumerator);
  4044   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
  4045   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  4046   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4047     cookieList.AppendObject(cookies[i]);
  4050   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  4053 namespace {
  4055 /**
  4056  * This structure is used as a in/out parameter when enumerating the cookies
  4057  * for an app.
  4058  * It will contain the app id and onlyBrowserElement flag information as input
  4059  * and will contain the array of matching cookies as output.
  4060  */
  4061 struct GetCookiesForAppStruct {
  4062   uint32_t              appId;
  4063   bool                  onlyBrowserElement;
  4064   nsCOMArray<nsICookie> cookies;
  4066   GetCookiesForAppStruct() MOZ_DELETE;
  4067   GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
  4068     : appId(aAppId)
  4069     , onlyBrowserElement(aOnlyBrowserElement)
  4070   {}
  4071 };
  4073 } // anonymous namespace
  4075 /* static */ PLDHashOperator
  4076 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
  4078   GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
  4080   if (entry->mAppId != data->appId ||
  4081       (data->onlyBrowserElement && !entry->mInBrowserElement)) {
  4082     return PL_DHASH_NEXT;
  4085   const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
  4087   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4088     data->cookies.AppendObject(cookies[i]);
  4091   return PL_DHASH_NEXT;
  4094 NS_IMETHODIMP
  4095 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
  4096                                   nsISimpleEnumerator** aEnumerator)
  4098   if (!mDBState) {
  4099     NS_WARNING("No DBState! Profile already closed?");
  4100     return NS_ERROR_NOT_AVAILABLE;
  4103   NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
  4105   GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
  4106   mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
  4108   return NS_NewArrayEnumerator(aEnumerator, data.cookies);
  4111 NS_IMETHODIMP
  4112 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
  4114   nsCOMPtr<nsISimpleEnumerator> enumerator;
  4115   nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
  4116                                  getter_AddRefs(enumerator));
  4118   NS_ENSURE_SUCCESS(rv, rv);
  4120   bool hasMore;
  4121   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
  4122     nsCOMPtr<nsISupports> supports;
  4123     nsCOMPtr<nsICookie> cookie;
  4124     rv = enumerator->GetNext(getter_AddRefs(supports));
  4125     NS_ENSURE_SUCCESS(rv, rv);
  4127     cookie = do_QueryInterface(supports);
  4129     nsAutoCString host;
  4130     cookie->GetHost(host);
  4132     nsAutoCString name;
  4133     cookie->GetName(name);
  4135     nsAutoCString path;
  4136     cookie->GetPath(path);
  4138     // nsICookie do not carry the appId/inBrowserElement information.
  4139     // That means we have to guess. This is easy for appId but not for
  4140     // inBrowserElement flag.
  4141     // A simple solution is to always ask to remove the cookie with
  4142     // inBrowserElement = true and only ask for the other one to be removed if
  4143     // we happen to be in the case of !aOnlyBrowserElement.
  4144     // Anyway, with this solution, we will likely be looking for unexistant
  4145     // cookies.
  4146     //
  4147     // NOTE: we could make this better by getting nsCookieEntry objects instead
  4148     // of plain nsICookie.
  4149     Remove(host, aAppId, true, name, path, false);
  4150     if (!aOnlyBrowserElement) {
  4151       Remove(host, aAppId, false, name, path, false);
  4155   return NS_OK;
  4158 // find an exact cookie specified by host, name, and path that hasn't expired.
  4159 // reveal the cookie only if its 1st party domain matches the (optional) origin.
  4160 bool
  4161 nsCookieService::FindCookie(const nsCookieKey    &aKey,
  4162                             const nsAFlatCString &aOrigin,
  4163                             const nsAFlatCString &aHost,
  4164                             const nsAFlatCString &aName,
  4165                             const nsAFlatCString &aPath,
  4166                             nsListIter           &aIter)
  4168   EnsureReadDomain(aKey);
  4170   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  4171   if (!entry)
  4172     return false;
  4174   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  4175   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4176     nsCookie *cookie = cookies[i];
  4178     if (aHost.Equals(cookie->Host()) &&
  4179         aPath.Equals(cookie->Path()) &&
  4180         aName.Equals(cookie->Name())) {
  4181         if (aOrigin.IsEmpty() || aOrigin.Equals(cookie->Origin())) {
  4182             aIter = nsListIter(entry, i);
  4183             return true;
  4188   return false;
  4191 // remove a cookie from the hashtable, and update the iterator state.
  4192 void
  4193 nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
  4194                                       mozIStorageBindingParamsArray *aParamsArray)
  4196   // if it's a non-session cookie, remove it from the db
  4197   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
  4198     // Use the asynchronous binding methods to ensure that we do not acquire
  4199     // the database lock.
  4200     mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  4201     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4202     if (!paramsArray) {
  4203       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4206     nsCOMPtr<mozIStorageBindingParams> params;
  4207     paramsArray->NewBindingParams(getter_AddRefs(params));
  4209     DebugOnly<nsresult> rv =
  4210       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4211                                    aIter.Cookie()->Name());
  4212     NS_ASSERT_SUCCESS(rv);
  4214     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4215                                       aIter.Cookie()->Host());
  4216     NS_ASSERT_SUCCESS(rv);
  4218     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4219                                       aIter.Cookie()->Path());
  4220     NS_ASSERT_SUCCESS(rv);
  4222     rv = paramsArray->AddParams(params);
  4223     NS_ASSERT_SUCCESS(rv);
  4225     // If we weren't given a params array, we'll need to remove it ourselves.
  4226     if (!aParamsArray) {
  4227       rv = stmt->BindParameters(paramsArray);
  4228       NS_ASSERT_SUCCESS(rv);
  4229       nsCOMPtr<mozIStoragePendingStatement> handle;
  4230       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  4231       NS_ASSERT_SUCCESS(rv);
  4235   if (aIter.entry->GetCookies().Length() == 1) {
  4236     // we're removing the last element in the array - so just remove the entry
  4237     // from the hash. note that the entryclass' dtor will take care of
  4238     // releasing this last element for us!
  4239     mDBState->hostTable.RawRemoveEntry(aIter.entry);
  4241   } else {
  4242     // just remove the element from the list
  4243     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
  4246   --mDBState->cookieCount;
  4249 void
  4250 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
  4251                      const nsCookieKey &aKey,
  4252                      const nsCookie *aCookie)
  4254   NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
  4255   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
  4257   // Use the asynchronous binding methods to ensure that we do not acquire the
  4258   // database lock.
  4259   nsCOMPtr<mozIStorageBindingParams> params;
  4260   DebugOnly<nsresult> rv =
  4261     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4262   NS_ASSERT_SUCCESS(rv);
  4264   // Bind our values to params
  4265   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
  4266                                     aKey.mBaseDomain);
  4267   NS_ASSERT_SUCCESS(rv);
  4269   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
  4270                                aKey.mAppId);
  4271   NS_ASSERT_SUCCESS(rv);
  4273   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
  4274                                aKey.mInBrowserElement ? 1 : 0);
  4275   NS_ASSERT_SUCCESS(rv);
  4277   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4278                                     aCookie->Name());
  4279   NS_ASSERT_SUCCESS(rv);
  4281   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
  4282                                     aCookie->Value());
  4283   NS_ASSERT_SUCCESS(rv);
  4285   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4286                                     aCookie->Host());
  4287   NS_ASSERT_SUCCESS(rv);
  4289   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4290                                     aCookie->Path());
  4291   NS_ASSERT_SUCCESS(rv);
  4293   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
  4294                                aCookie->Expiry());
  4295   NS_ASSERT_SUCCESS(rv);
  4297   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4298                                aCookie->LastAccessed());
  4299   NS_ASSERT_SUCCESS(rv);
  4301   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
  4302                                aCookie->CreationTime());
  4303   NS_ASSERT_SUCCESS(rv);
  4305   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
  4306                                aCookie->IsSecure());
  4307   NS_ASSERT_SUCCESS(rv);
  4309   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
  4310                                aCookie->IsHttpOnly());
  4311   NS_ASSERT_SUCCESS(rv);
  4313   // Bind the params to the array.
  4314   rv = aParamsArray->AddParams(params);
  4315   NS_ASSERT_SUCCESS(rv);
  4318 void
  4319 nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
  4320                                  nsCookie                      *aCookie,
  4321                                  DBState                       *aDBState,
  4322                                  mozIStorageBindingParamsArray *aParamsArray,
  4323                                  bool                           aWriteToDB)
  4325   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
  4326                "Not writing to the DB but have a params array?");
  4327   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
  4328                "Do not have a DB connection but have a params array?");
  4330   nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
  4331   NS_ASSERTION(entry, "can't insert element into a null entry!");
  4333   entry->GetCookies().AppendElement(aCookie);
  4334   ++aDBState->cookieCount;
  4336   // keep track of the oldest cookie, for when it comes time to purge
  4337   if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
  4338     aDBState->cookieOldestTime = aCookie->LastAccessed();
  4340   // if it's a non-session cookie and hasn't just been read from the db, write it out.
  4341   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
  4342     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
  4343     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4344     if (!paramsArray) {
  4345       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4347     bindCookieParameters(paramsArray, aKey, aCookie);
  4349     // If we were supplied an array to store parameters, we shouldn't call
  4350     // executeAsync - someone up the stack will do this for us.
  4351     if (!aParamsArray) {
  4352       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  4353       NS_ASSERT_SUCCESS(rv);
  4354       nsCOMPtr<mozIStoragePendingStatement> handle;
  4355       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
  4356       NS_ASSERT_SUCCESS(rv);
  4361 void
  4362 nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
  4363                                     int64_t                        aLastAccessed,
  4364                                     mozIStorageBindingParamsArray *aParamsArray)
  4366   NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
  4368   // udpate the lastAccessed timestamp
  4369   aCookie->SetLastAccessed(aLastAccessed);
  4371   // if it's a non-session cookie, update it in the db too
  4372   if (!aCookie->IsSession() && aParamsArray) {
  4373     // Create our params holder.
  4374     nsCOMPtr<mozIStorageBindingParams> params;
  4375     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4377     // Bind our parameters.
  4378     DebugOnly<nsresult> rv =
  4379       params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4380                               aLastAccessed);
  4381     NS_ASSERT_SUCCESS(rv);
  4383     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4384                                       aCookie->Name());
  4385     NS_ASSERT_SUCCESS(rv);
  4387     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4388                                       aCookie->Host());
  4389     NS_ASSERT_SUCCESS(rv);
  4391     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4392                                       aCookie->Path());
  4393     NS_ASSERT_SUCCESS(rv);
  4395     // Add our bound parameters to the array.
  4396     rv = aParamsArray->AddParams(params);
  4397     NS_ASSERT_SUCCESS(rv);
  4401 size_t
  4402 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  4404   size_t n = aMallocSizeOf(this);
  4406   if (mDefaultDBState) {
  4407     n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
  4409   if (mPrivateDBState) {
  4410     n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
  4413   return n;
  4416 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
  4418 NS_IMETHODIMP
  4419 nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
  4420                                 nsISupports* aData)
  4422   return MOZ_COLLECT_REPORT(
  4423     "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
  4424     SizeOfIncludingThis(CookieServiceMallocSizeOf),
  4425     "Memory used by the cookie service.");

mercurial