netwerk/cookie/nsCookieService.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
child 4
fc2d59ddac77
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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,("path: %s\n", aCookie->Path().get()));
   267     PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
   268                    PR_GMTParameters, &explodedTime);
   269     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   270     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   271       ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
   273     PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
   274     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
   275     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("created: %s", timeString));
   277     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
   278     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
   279   }
   280 }
   282 static void
   283 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
   284 {
   285   // if logging isn't enabled, return now to save cycles
   286   if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG)) {
   287     return;
   288   }
   290   nsAutoCString spec;
   291   if (aHostURI)
   292     aHostURI->GetAsciiSpec(spec);
   294   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,
   295     ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
   296   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("request URL: %s\n", spec.get()));
   297   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("cookie string: %s\n", aCookieString));
   298   if (aSetCookie)
   299     PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
   301   LogCookie(aCookie);
   303   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   304 }
   306 static void
   307 LogEvicted(nsCookie *aCookie, const char* details)
   308 {
   309   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n"));
   310   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s\n", details));
   312   LogCookie(aCookie);
   314   PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
   315 }
   317 // inline wrappers to make passing in nsAFlatCStrings easier
   318 static inline void
   319 LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
   320 {
   321   LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
   322 }
   324 static inline void
   325 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
   326 {
   327   LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
   328 }
   330 #else
   331 #define COOKIE_LOGFAILURE(a, b, c, d)    PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   332 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   333 #define COOKIE_LOGEVICTED(a, b)          PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   334 #define COOKIE_LOGSTRING(a, b)           PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   335 #endif
   337 #ifdef DEBUG
   338 #define NS_ASSERT_SUCCESS(res)                                               \
   339   PR_BEGIN_MACRO                                                             \
   340   nsresult __rv = res; /* Do not evaluate |res| more than once! */           \
   341   if (NS_FAILED(__rv)) {                                                     \
   342     char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
   343                             #res, __rv);                                     \
   344     NS_ASSERTION(NS_SUCCEEDED(__rv), msg);                                   \
   345     PR_smprintf_free(msg);                                                   \
   346   }                                                                          \
   347   PR_END_MACRO
   348 #else
   349 #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
   350 #endif
   352 /******************************************************************************
   353  * DBListenerErrorHandler impl:
   354  * Parent class for our async storage listeners that handles the logging of
   355  * errors.
   356  ******************************************************************************/
   357 class DBListenerErrorHandler : public mozIStorageStatementCallback
   358 {
   359 protected:
   360   DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
   361   nsRefPtr<DBState> mDBState;
   362   virtual const char *GetOpType() = 0;
   364 public:
   365   NS_IMETHOD HandleError(mozIStorageError* aError)
   366   {
   367     int32_t result = -1;
   368     aError->GetResult(&result);
   370 #ifdef PR_LOGGING
   371     nsAutoCString message;
   372     aError->GetMessage(message);
   373     COOKIE_LOGSTRING(PR_LOG_WARNING,
   374       ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
   375        "performing operation '%s' with message '%s'; rebuilding database.",
   376        result, GetOpType(), message.get()));
   377 #endif
   379     // Rebuild the database.
   380     gCookieService->HandleCorruptDB(mDBState);
   382     return NS_OK;
   383   }
   384 };
   386 /******************************************************************************
   387  * InsertCookieDBListener impl:
   388  * mozIStorageStatementCallback used to track asynchronous insertion operations.
   389  ******************************************************************************/
   390 class InsertCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   391 {
   392 protected:
   393   virtual const char *GetOpType() { return "INSERT"; }
   395 public:
   396   NS_DECL_ISUPPORTS
   398   InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   399   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   400   {
   401     NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
   402     return NS_OK;
   403   }
   404   NS_IMETHOD HandleCompletion(uint16_t aReason)
   405   {
   406     // If we were rebuilding the db and we succeeded, make our corruptFlag say
   407     // so.
   408     if (mDBState->corruptFlag == DBState::REBUILDING &&
   409         aReason == mozIStorageStatementCallback::REASON_FINISHED) {
   410       COOKIE_LOGSTRING(PR_LOG_DEBUG,
   411         ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
   412       mDBState->corruptFlag = DBState::OK;
   413     }
   414     return NS_OK;
   415   }
   416 };
   418 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
   420 /******************************************************************************
   421  * UpdateCookieDBListener impl:
   422  * mozIStorageStatementCallback used to track asynchronous update operations.
   423  ******************************************************************************/
   424 class UpdateCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   425 {
   426 protected:
   427   virtual const char *GetOpType() { return "UPDATE"; }
   429 public:
   430   NS_DECL_ISUPPORTS
   432   UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   433   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   434   {
   435     NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
   436     return NS_OK;
   437   }
   438   NS_IMETHOD HandleCompletion(uint16_t aReason)
   439   {
   440     return NS_OK;
   441   }
   442 };
   444 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
   446 /******************************************************************************
   447  * RemoveCookieDBListener impl:
   448  * mozIStorageStatementCallback used to track asynchronous removal operations.
   449  ******************************************************************************/
   450 class RemoveCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   451 {
   452 protected:
   453   virtual const char *GetOpType() { return "REMOVE"; }
   455 public:
   456   NS_DECL_ISUPPORTS
   458   RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
   459   NS_IMETHOD HandleResult(mozIStorageResultSet*)
   460   {
   461     NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
   462     return NS_OK;
   463   }
   464   NS_IMETHOD HandleCompletion(uint16_t aReason)
   465   {
   466     return NS_OK;
   467   }
   468 };
   470 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
   472 /******************************************************************************
   473  * ReadCookieDBListener impl:
   474  * mozIStorageStatementCallback used to track asynchronous removal operations.
   475  ******************************************************************************/
   476 class ReadCookieDBListener MOZ_FINAL : public DBListenerErrorHandler
   477 {
   478 protected:
   479   virtual const char *GetOpType() { return "READ"; }
   480   bool mCanceled;
   482 public:
   483   NS_DECL_ISUPPORTS
   485   ReadCookieDBListener(DBState* dbState)
   486     : DBListenerErrorHandler(dbState)
   487     , mCanceled(false)
   488   {
   489   }
   491   void Cancel() { mCanceled = true; }
   493   NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
   494   {
   495     nsCOMPtr<mozIStorageRow> row;
   497     while (1) {
   498       DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
   499       NS_ASSERT_SUCCESS(rv);
   501       if (!row)
   502         break;
   504       CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
   505       row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
   506       tuple->key.mAppId = static_cast<uint32_t>(row->AsInt32(IDX_APP_ID));
   507       tuple->key.mInBrowserElement = static_cast<bool>(row->AsInt32(IDX_BROWSER_ELEM));
   508       tuple->cookie = gCookieService->GetCookieFromRow(row);
   509     }
   511     return NS_OK;
   512   }
   513   NS_IMETHOD HandleCompletion(uint16_t aReason)
   514   {
   515     // Process the completion of the read operation. If we have been canceled,
   516     // we cannot assume that the cookieservice still has an open connection
   517     // or that it even refers to the same database, so we must return early.
   518     // Conversely, the cookieservice guarantees that if we have not been
   519     // canceled, the database connection is still alive and we can safely
   520     // operate on it.
   522     if (mCanceled) {
   523       // We may receive a REASON_FINISHED after being canceled;
   524       // tweak the reason accordingly.
   525       aReason = mozIStorageStatementCallback::REASON_CANCELED;
   526     }
   528     switch (aReason) {
   529     case mozIStorageStatementCallback::REASON_FINISHED:
   530       gCookieService->AsyncReadComplete();
   531       break;
   532     case mozIStorageStatementCallback::REASON_CANCELED:
   533       // Nothing more to do here. The partially read data has already been
   534       // thrown away.
   535       COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled"));
   536       break;
   537     case mozIStorageStatementCallback::REASON_ERROR:
   538       // Nothing more to do here. DBListenerErrorHandler::HandleError()
   539       // can handle it.
   540       COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error"));
   541       break;
   542     default:
   543       NS_NOTREACHED("invalid reason");
   544     }
   545     return NS_OK;
   546   }
   547 };
   549 NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
   551 /******************************************************************************
   552  * CloseCookieDBListener imp:
   553  * Static mozIStorageCompletionCallback used to notify when the database is
   554  * successfully closed.
   555  ******************************************************************************/
   556 class CloseCookieDBListener MOZ_FINAL :  public mozIStorageCompletionCallback
   557 {
   558 public:
   559   CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
   560   nsRefPtr<DBState> mDBState;
   561   NS_DECL_ISUPPORTS
   563   NS_IMETHOD Complete(nsresult, nsISupports*)
   564   {
   565     gCookieService->HandleDBClosed(mDBState);
   566     return NS_OK;
   567   }
   568 };
   570 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
   572 namespace {
   574 class AppClearDataObserver MOZ_FINAL : public nsIObserver {
   575 public:
   576   NS_DECL_ISUPPORTS
   578   // nsIObserver implementation.
   579   NS_IMETHODIMP
   580   Observe(nsISupports *aSubject, const char *aTopic, const char16_t *data)
   581   {
   582     MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA));
   584     uint32_t appId = NECKO_UNKNOWN_APP_ID;
   585     bool browserOnly = false;
   586     nsresult rv = NS_GetAppInfoFromClearDataNotification(aSubject, &appId,
   587                                                          &browserOnly);
   588     NS_ENSURE_SUCCESS(rv, rv);
   590     nsCOMPtr<nsICookieManager2> cookieManager
   591       = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
   592     MOZ_ASSERT(cookieManager);
   593     return cookieManager->RemoveCookiesForApp(appId, browserOnly);
   594   }
   595 };
   597 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
   599 } // anonymous namespace
   601 size_t
   602 nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   603 {
   604   return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   605 }
   607 size_t
   608 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   609 {
   610   size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
   612   amount += mCookies.SizeOfExcludingThis(aMallocSizeOf);
   613   for (uint32_t i = 0; i < mCookies.Length(); ++i) {
   614     amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
   615   }
   617   return amount;
   618 }
   620 static size_t
   621 HostTableEntrySizeOfExcludingThis(nsCookieEntry *aEntry,
   622                                   MallocSizeOf aMallocSizeOf,
   623                                   void *arg)
   624 {
   625   return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   626 }
   628 size_t
   629 CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   630 {
   631   size_t amount = 0;
   633   amount += key.SizeOfExcludingThis(aMallocSizeOf);
   634   amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
   636   return amount;
   637 }
   639 static size_t
   640 ReadSetEntrySizeOfExcludingThis(nsCookieKey *aEntry,
   641                                 MallocSizeOf aMallocSizeOf,
   642                                 void *)
   643 {
   644   return aEntry->SizeOfExcludingThis(aMallocSizeOf);
   645 }
   647 size_t
   648 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   649 {
   650   size_t amount = 0;
   652   amount += aMallocSizeOf(this);
   653   amount += hostTable.SizeOfExcludingThis(HostTableEntrySizeOfExcludingThis,
   654                                           aMallocSizeOf);
   655   amount += hostArray.SizeOfExcludingThis(aMallocSizeOf);
   656   for (uint32_t i = 0; i < hostArray.Length(); ++i) {
   657     amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
   658   }
   659   amount += readSet.SizeOfExcludingThis(ReadSetEntrySizeOfExcludingThis,
   660                                         aMallocSizeOf);
   662   return amount;
   663 }
   665 /******************************************************************************
   666  * nsCookieService impl:
   667  * singleton instance ctor/dtor methods
   668  ******************************************************************************/
   670 nsICookieService*
   671 nsCookieService::GetXPCOMSingleton()
   672 {
   673   if (IsNeckoChild())
   674     return CookieServiceChild::GetSingleton();
   676   return GetSingleton();
   677 }
   679 nsCookieService*
   680 nsCookieService::GetSingleton()
   681 {
   682   NS_ASSERTION(!IsNeckoChild(), "not a parent process");
   684   if (gCookieService) {
   685     NS_ADDREF(gCookieService);
   686     return gCookieService;
   687   }
   689   // Create a new singleton nsCookieService.
   690   // We AddRef only once since XPCOM has rules about the ordering of module
   691   // teardowns - by the time our module destructor is called, it's too late to
   692   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
   693   // cycles have already been completed and would result in serious leaks.
   694   // See bug 209571.
   695   gCookieService = new nsCookieService();
   696   if (gCookieService) {
   697     NS_ADDREF(gCookieService);
   698     if (NS_FAILED(gCookieService->Init())) {
   699       NS_RELEASE(gCookieService);
   700     }
   701   }
   703   return gCookieService;
   704 }
   706 /* static */ void
   707 nsCookieService::AppClearDataObserverInit()
   708 {
   709   nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
   710   nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
   711   observerService->AddObserver(obs, TOPIC_WEB_APP_CLEAR_DATA,
   712                                /* holdsWeak= */ false);
   713 }
   715 /******************************************************************************
   716  * nsCookieService impl:
   717  * public methods
   718  ******************************************************************************/
   720 NS_IMPL_ISUPPORTS(nsCookieService,
   721                   nsICookieService,
   722                   nsICookieManager,
   723                   nsICookieManager2,
   724                   nsIObserver,
   725                   nsISupportsWeakReference,
   726                   nsIMemoryReporter)
   728 nsCookieService::nsCookieService()
   729  : mDBState(nullptr)
   730  , mCookieBehavior(BEHAVIOR_ACCEPT)
   731  , mThirdPartySession(false)
   732  , mMaxNumberOfCookies(kMaxNumberOfCookies)
   733  , mMaxCookiesPerHost(kMaxCookiesPerHost)
   734  , mCookiePurgeAge(kCookiePurgeAge)
   735 {
   736 }
   738 nsresult
   739 nsCookieService::Init()
   740 {
   741   nsresult rv;
   742   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
   743   NS_ENSURE_SUCCESS(rv, rv);
   745   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
   746   NS_ENSURE_SUCCESS(rv, rv);
   748   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   749   NS_ENSURE_SUCCESS(rv, rv);
   751   // init our pref and observer
   752   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   753   if (prefBranch) {
   754     prefBranch->AddObserver(kPrefCookieBehavior,     this, true);
   755     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
   756     prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, true);
   757     prefBranch->AddObserver(kPrefCookiePurgeAge,     this, true);
   758     prefBranch->AddObserver(kPrefThirdPartySession,  this, true);
   759     PrefChanged(prefBranch);
   760   }
   762   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
   763   NS_ENSURE_SUCCESS(rv, rv);
   765   // Init our default, and possibly private DBStates.
   766   InitDBStates();
   768   RegisterWeakMemoryReporter(this);
   770   mObserverService = mozilla::services::GetObserverService();
   771   NS_ENSURE_STATE(mObserverService);
   772   mObserverService->AddObserver(this, "profile-before-change", true);
   773   mObserverService->AddObserver(this, "profile-do-change", true);
   774   mObserverService->AddObserver(this, "last-pb-context-exited", true);
   776   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
   777   if (!mPermissionService) {
   778     NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
   779     COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available"));
   780   }
   782   return NS_OK;
   783 }
   785 void
   786 nsCookieService::InitDBStates()
   787 {
   788   NS_ASSERTION(!mDBState, "already have a DBState");
   789   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
   790   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
   792   // Create a new default DBState and set our current one.
   793   mDefaultDBState = new DBState();
   794   mDBState = mDefaultDBState;
   796   mPrivateDBState = new DBState();
   798   // Get our cookie file.
   799   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   800     getter_AddRefs(mDefaultDBState->cookieFile));
   801   if (NS_FAILED(rv)) {
   802     // We've already set up our DBStates appropriately; nothing more to do.
   803     COOKIE_LOGSTRING(PR_LOG_WARNING,
   804       ("InitDBStates(): couldn't get cookie file"));
   805     return;
   806   }
   807   mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
   809   // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
   810   // do so.
   811   OpenDBResult result = TryInitDB(false);
   812   if (result == RESULT_RETRY) {
   813     // Database may be corrupt. Synchronously close the connection, clean up the
   814     // default DBState, and try again.
   815     COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()"));
   816     CleanupCachedStatements();
   817     CleanupDefaultDBConnection();
   818     result = TryInitDB(true);
   819     if (result == RESULT_RETRY) {
   820       // We're done. Change the code to failure so we clean up below.
   821       result = RESULT_FAILURE;
   822     }
   823   }
   825   if (result == RESULT_FAILURE) {
   826     COOKIE_LOGSTRING(PR_LOG_WARNING,
   827       ("InitDBStates(): TryInitDB() failed, closing connection"));
   829     // Connection failure is unrecoverable. Clean up our connection. We can run
   830     // fine without persistent storage -- e.g. if there's no profile.
   831     CleanupCachedStatements();
   832     CleanupDefaultDBConnection();
   833   }
   834 }
   836 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
   837  * move the existing database file out of the way and create a new one.
   838  *
   839  * @returns RESULT_OK if opening or creating the database succeeded;
   840  *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
   841  *          other failure occurred that might be resolved by recreating the
   842  *          database; or RESULT_FAILED if there was an unrecoverable error and
   843  *          we must run without a database.
   844  *
   845  * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
   846  * cleanup of the default DBState.
   847  */
   848 OpenDBResult
   849 nsCookieService::TryInitDB(bool aRecreateDB)
   850 {
   851   NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
   852   NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
   853   NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
   854   NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
   856   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
   857   // want to delete it outright, since it may be useful for debugging purposes,
   858   // so we move it out of the way.
   859   nsresult rv;
   860   if (aRecreateDB) {
   861     nsCOMPtr<nsIFile> backupFile;
   862     mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
   863     rv = backupFile->MoveToNative(nullptr,
   864       NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
   865     NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
   866   }
   868   // This block provides scope for the Telemetry AutoTimer
   869   {
   870     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
   871       telemetry;
   872     ReadAheadFile(mDefaultDBState->cookieFile);
   874     // open a connection to the cookie database, and only cache our connection
   875     // and statements upon success. The connection is opened unshared to eliminate
   876     // cache contention between the main and background threads.
   877     rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
   878       getter_AddRefs(mDefaultDBState->dbConn));
   879     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   880   }
   882   // Set up our listeners.
   883   mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
   884   mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
   885   mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
   886   mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
   888   // Grow cookie db in 512KB increments
   889   mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
   891   bool tableExists = false;
   892   mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
   893     &tableExists);
   894   if (!tableExists) {
   895     rv = CreateTable();
   896     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   898   } else {
   899     // table already exists; check the schema version before reading
   900     int32_t dbSchemaVersion;
   901     rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
   902     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   904     // Start a transaction for the whole migration block.
   905     mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
   907     switch (dbSchemaVersion) {
   908     // Upgrading.
   909     // Every time you increment the database schema, you need to implement
   910     // the upgrading code from the previous version to the new one. If migration
   911     // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
   912     // the original database will be saved, in the hopes that we might one day
   913     // see it and fix it.
   914     case 1:
   915       {
   916         // Add the lastAccessed column to the table.
   917         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   918           "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
   919         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   920       }
   921       // Fall through to the next upgrade.
   923     case 2:
   924       {
   925         // Add the baseDomain column and index to the table.
   926         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   927           "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
   928         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   930         // Compute the baseDomains for the table. This must be done eagerly
   931         // otherwise we won't be able to synchronously read in individual
   932         // domains on demand.
   933         const int64_t SCHEMA2_IDX_ID  =  0;
   934         const int64_t SCHEMA2_IDX_HOST = 1;
   935         nsCOMPtr<mozIStorageStatement> select;
   936         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   937           "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
   938         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   940         nsCOMPtr<mozIStorageStatement> update;
   941         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
   942           "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
   943           getter_AddRefs(update));
   944         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   946         nsCString baseDomain, host;
   947         bool hasResult;
   948         while (1) {
   949           rv = select->ExecuteStep(&hasResult);
   950           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   952           if (!hasResult)
   953             break;
   955           int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
   956           select->GetUTF8String(SCHEMA2_IDX_HOST, host);
   958           rv = GetBaseDomainFromHost(host, baseDomain);
   959           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   961           mozStorageStatementScoper scoper(update);
   963           rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
   964                                             baseDomain);
   965           NS_ASSERT_SUCCESS(rv);
   966           rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
   967                                        id);
   968           NS_ASSERT_SUCCESS(rv);
   970           rv = update->ExecuteStep(&hasResult);
   971           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   972         }
   974         // Create an index on baseDomain.
   975         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   976           "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
   977         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
   978       }
   979       // Fall through to the next upgrade.
   981     case 3:
   982       {
   983         // Add the creationTime column to the table, and create a unique index
   984         // on (name, host, path). Before we do this, we have to purge the table
   985         // of expired cookies such that we know that the (name, host, path)
   986         // index is truly unique -- otherwise we can't create the index. Note
   987         // that we can't just execute a statement to delete all rows where the
   988         // expiry column is in the past -- doing so would rely on the clock
   989         // (both now and when previous cookies were set) being monotonic.
   991         // Select the whole table, and order by the fields we're interested in.
   992         // This means we can simply do a linear traversal of the results and
   993         // check for duplicates as we go.
   994         const int64_t SCHEMA3_IDX_ID =   0;
   995         const int64_t SCHEMA3_IDX_NAME = 1;
   996         const int64_t SCHEMA3_IDX_HOST = 2;
   997         const int64_t SCHEMA3_IDX_PATH = 3;
   998         nsCOMPtr<mozIStorageStatement> select;
   999         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1000           "SELECT id, name, host, path FROM moz_cookies "
  1001             "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
  1002           getter_AddRefs(select));
  1003         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1005         nsCOMPtr<mozIStorageStatement> deleteExpired;
  1006         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1007           "DELETE FROM moz_cookies WHERE id = :id"),
  1008           getter_AddRefs(deleteExpired));
  1009         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1011         // Read the first row.
  1012         bool hasResult;
  1013         rv = select->ExecuteStep(&hasResult);
  1014         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1016         if (hasResult) {
  1017           nsCString name1, host1, path1;
  1018           int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
  1019           select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
  1020           select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
  1021           select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
  1023           nsCString name2, host2, path2;
  1024           while (1) {
  1025             // Read the second row.
  1026             rv = select->ExecuteStep(&hasResult);
  1027             NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1029             if (!hasResult)
  1030               break;
  1032             int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
  1033             select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
  1034             select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
  1035             select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
  1037             // If the two rows match in (name, host, path), we know the earlier
  1038             // row has an earlier expiry time. Delete it.
  1039             if (name1 == name2 && host1 == host2 && path1 == path2) {
  1040               mozStorageStatementScoper scoper(deleteExpired);
  1042               rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
  1043                 id1);
  1044               NS_ASSERT_SUCCESS(rv);
  1046               rv = deleteExpired->ExecuteStep(&hasResult);
  1047               NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1050             // Make the second row the first for the next iteration.
  1051             name1 = name2;
  1052             host1 = host2;
  1053             path1 = path2;
  1054             id1 = id2;
  1058         // Add the creationTime column to the table.
  1059         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1060           "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
  1061         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1063         // Copy the id of each row into the new creationTime column.
  1064         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1065           "UPDATE moz_cookies SET creationTime = "
  1066             "(SELECT id WHERE id = moz_cookies.id)"));
  1067         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1069         // Create a unique index on (name, host, path) to allow fast lookup.
  1070         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1071           "CREATE UNIQUE INDEX moz_uniqueid "
  1072           "ON moz_cookies (name, host, path)"));
  1073         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1075       // Fall through to the next upgrade.
  1077     case 4:
  1079         // We need to add appId/inBrowserElement, plus change a constraint on
  1080         // the table (unique entries now include appId/inBrowserElement):
  1081         // this requires creating a new table and copying the data to it.  We
  1082         // then rename the new table to the old name.
  1083         //
  1084         // Why we made this change: appId/inBrowserElement allow "cookie jars"
  1085         // for Firefox OS. We create a separate cookie namespace per {appId,
  1086         // inBrowserElement}.  When upgrading, we convert existing cookies
  1087         // (which imply we're on desktop/mobile) to use {0, false}, as that is
  1088         // the only namespace used by a non-Firefox-OS implementation.
  1090         // Rename existing table
  1091         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1092           "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
  1093         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1095         // Drop existing index (CreateTable will create new one for new table)
  1096         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1097           "DROP INDEX moz_basedomain"));
  1098         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1100         // Create new table (with new fields and new unique constraint)
  1101         rv = CreateTable();
  1102         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1104         // Copy data from old table, using appId/inBrowser=0 for existing rows
  1105         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1106           "INSERT INTO moz_cookies "
  1107           "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
  1108           " lastAccessed, creationTime, isSecure, isHttpOnly) "
  1109           "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
  1110           " lastAccessed, creationTime, isSecure, isHttpOnly "
  1111           "FROM moz_cookies_old"));
  1112         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1114         // Drop old table
  1115         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1116           "DROP TABLE moz_cookies_old"));
  1117         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1119         COOKIE_LOGSTRING(PR_LOG_DEBUG, 
  1120           ("Upgraded database to schema version 5"));
  1123       // No more upgrades. Update the schema version.
  1124       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1125       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1127     case COOKIES_SCHEMA_VERSION:
  1128       break;
  1130     case 0:
  1132         NS_WARNING("couldn't get schema version!");
  1134         // the table may be usable; someone might've just clobbered the schema
  1135         // version. we can treat this case like a downgrade using the codepath
  1136         // below, by verifying the columns we care about are all there. for now,
  1137         // re-set the schema version in the db, in case the checks succeed (if
  1138         // they don't, we're dropping the table anyway).
  1139         rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
  1140         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1142       // fall through to downgrade check
  1144     // downgrading.
  1145     // if columns have been added to the table, we can still use the ones we
  1146     // understand safely. if columns have been deleted or altered, just
  1147     // blow away the table and start from scratch! if you change the way
  1148     // a column is interpreted, make sure you also change its name so this
  1149     // check will catch it.
  1150     default:
  1152         // check if all the expected columns exist
  1153         nsCOMPtr<mozIStorageStatement> stmt;
  1154         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
  1155           "SELECT "
  1156             "id, "
  1157             "baseDomain, "
  1158             "appId, "
  1159             "inBrowserElement, "
  1160             "name, "
  1161             "value, "
  1162             "host, "
  1163             "path, "
  1164             "expiry, "
  1165             "lastAccessed, "
  1166             "creationTime, "
  1167             "isSecure, "
  1168             "isHttpOnly "
  1169           "FROM moz_cookies"), getter_AddRefs(stmt));
  1170         if (NS_SUCCEEDED(rv))
  1171           break;
  1173         // our columns aren't there - drop the table!
  1174         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1175           "DROP TABLE moz_cookies"));
  1176         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1178         rv = CreateTable();
  1179         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1181       break;
  1185   // make operations on the table asynchronous, for performance
  1186   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1187     "PRAGMA synchronous = OFF"));
  1189   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
  1190   // 16 pages (around 500KB).
  1191   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1192     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
  1193   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1194     "PRAGMA wal_autocheckpoint = 16"));
  1196   // cache frequently used statements (for insertion, deletion, and updating)
  1197   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1198     "INSERT INTO moz_cookies ("
  1199       "baseDomain, "
  1200       "appId, "
  1201       "inBrowserElement, "
  1202       "name, "
  1203       "value, "
  1204       "host, "
  1205       "path, "
  1206       "expiry, "
  1207       "lastAccessed, "
  1208       "creationTime, "
  1209       "isSecure, "
  1210       "isHttpOnly"
  1211     ") VALUES ("
  1212       ":baseDomain, "
  1213       ":appId, "
  1214       ":inBrowserElement, "
  1215       ":name, "
  1216       ":value, "
  1217       ":host, "
  1218       ":path, "
  1219       ":expiry, "
  1220       ":lastAccessed, "
  1221       ":creationTime, "
  1222       ":isSecure, "
  1223       ":isHttpOnly"
  1224     ")"),
  1225     getter_AddRefs(mDefaultDBState->stmtInsert));
  1226   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1228   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1229     "DELETE FROM moz_cookies "
  1230     "WHERE name = :name AND host = :host AND path = :path"),
  1231     getter_AddRefs(mDefaultDBState->stmtDelete));
  1232   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1234   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1235     "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
  1236     "WHERE name = :name AND host = :host AND path = :path"),
  1237     getter_AddRefs(mDefaultDBState->stmtUpdate));
  1238   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  1240   // if we deleted a corrupt db, don't attempt to import - return now
  1241   if (aRecreateDB)
  1242     return RESULT_OK;
  1244   // check whether to import or just read in the db
  1245   if (tableExists)
  1246     return Read();
  1248   nsCOMPtr<nsIFile> oldCookieFile;
  1249   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
  1250     getter_AddRefs(oldCookieFile));
  1251   if (NS_FAILED(rv)) return RESULT_OK;
  1253   // Import cookies, and clean up the old file regardless of success or failure.
  1254   // Note that we have to switch out our DBState temporarily, in case we're in
  1255   // private browsing mode; otherwise ImportCookies() won't be happy.
  1256   DBState* initialState = mDBState;
  1257   mDBState = mDefaultDBState;
  1258   oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
  1259   ImportCookies(oldCookieFile);
  1260   oldCookieFile->Remove(false);
  1261   mDBState = initialState;
  1263   return RESULT_OK;
  1266 // Sets the schema version and creates the moz_cookies table.
  1267 nsresult
  1268 nsCookieService::CreateTable()
  1270   // Set the schema version, before creating the table.
  1271   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
  1272     COOKIES_SCHEMA_VERSION);
  1273   if (NS_FAILED(rv)) return rv;
  1275   // Create the table.  We default appId/inBrowserElement to 0: this is so if
  1276   // users revert to an older Firefox version that doesn't know about these
  1277   // fields, any cookies set will still work once they upgrade back.
  1278   rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1279     "CREATE TABLE moz_cookies ("
  1280       "id INTEGER PRIMARY KEY, "
  1281       "baseDomain TEXT, "
  1282       "appId INTEGER DEFAULT 0, "
  1283       "inBrowserElement INTEGER DEFAULT 0, "
  1284       "name TEXT, "
  1285       "value TEXT, "
  1286       "host TEXT, "
  1287       "path TEXT, "
  1288       "expiry INTEGER, "
  1289       "lastAccessed INTEGER, "
  1290       "creationTime INTEGER, "
  1291       "isSecure INTEGER, "
  1292       "isHttpOnly INTEGER, "
  1293       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
  1294     ")"));
  1295   if (NS_FAILED(rv)) return rv;
  1297   // Create an index on baseDomain.
  1298   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
  1299     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
  1300                                                 "appId, "
  1301                                                 "inBrowserElement)"));
  1304 void
  1305 nsCookieService::CloseDBStates()
  1307   // Null out our private and pointer DBStates regardless.
  1308   mPrivateDBState = nullptr;
  1309   mDBState = nullptr;
  1311   // If we don't have a default DBState, we're done.
  1312   if (!mDefaultDBState)
  1313     return;
  1315   // Cleanup cached statements before we can close anything.
  1316   CleanupCachedStatements();
  1318   if (mDefaultDBState->dbConn) {
  1319     // Cancel any pending read. No further results will be received by our
  1320     // read listener.
  1321     if (mDefaultDBState->pendingRead) {
  1322       CancelAsyncRead(true);
  1325     // Asynchronously close the connection. We will null it below.
  1326     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1329   CleanupDefaultDBConnection();
  1331   mDefaultDBState = nullptr;
  1334 // Null out the statements.
  1335 // This must be done before closing the connection.
  1336 void
  1337 nsCookieService::CleanupCachedStatements()
  1339   mDefaultDBState->stmtInsert = nullptr;
  1340   mDefaultDBState->stmtDelete = nullptr;
  1341   mDefaultDBState->stmtUpdate = nullptr;
  1344 // Null out the listeners, and the database connection itself. This
  1345 // will not null out the statements, cancel a pending read or
  1346 // asynchronously close the connection -- these must be done
  1347 // beforehand if necessary.
  1348 void
  1349 nsCookieService::CleanupDefaultDBConnection()
  1351   MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
  1352   MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
  1353   MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
  1355   // Null out the database connections. If 'dbConn' has not been used for any
  1356   // asynchronous operations yet, this will synchronously close it; otherwise,
  1357   // it's expected that the caller has performed an AsyncClose prior.
  1358   mDefaultDBState->dbConn = nullptr;
  1359   mDefaultDBState->syncConn = nullptr;
  1361   // Manually null out our listeners. This is necessary because they hold a
  1362   // strong ref to the DBState itself. They'll stay alive until whatever
  1363   // statements are still executing complete.
  1364   mDefaultDBState->readListener = nullptr;
  1365   mDefaultDBState->insertListener = nullptr;
  1366   mDefaultDBState->updateListener = nullptr;
  1367   mDefaultDBState->removeListener = nullptr;
  1368   mDefaultDBState->closeListener = nullptr;
  1371 void
  1372 nsCookieService::HandleDBClosed(DBState* aDBState)
  1374   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1375     ("HandleDBClosed(): DBState %x closed", aDBState));
  1377   switch (aDBState->corruptFlag) {
  1378   case DBState::OK: {
  1379     // Database is healthy. Notify of closure.
  1380     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1381     break;
  1383   case DBState::CLOSING_FOR_REBUILD: {
  1384     // Our close finished. Start the rebuild, and notify of db closure later.
  1385     RebuildCorruptDB(aDBState);
  1386     break;
  1388   case DBState::REBUILDING: {
  1389     // We encountered an error during rebuild, closed the database, and now
  1390     // here we are. We already have a 'cookies.sqlite.bak' from the original
  1391     // dead database; we don't want to overwrite it, so let's move this one to
  1392     // 'cookies.sqlite.bak-rebuild'.
  1393     nsCOMPtr<nsIFile> backupFile;
  1394     aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
  1395     nsresult rv = backupFile->MoveToNative(nullptr,
  1396       NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
  1398     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1399       ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
  1400        "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
  1401     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1402     break;
  1407 void
  1408 nsCookieService::HandleCorruptDB(DBState* aDBState)
  1410   if (mDefaultDBState != aDBState) {
  1411     // We've either closed the state or we've switched profiles. It's getting
  1412     // a bit late to rebuild -- bail instead.
  1413     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1414       ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
  1415     return;
  1418   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1419     ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
  1420       aDBState->corruptFlag));
  1422   // Mark the database corrupt, so the close listener can begin reconstructing
  1423   // it.
  1424   switch (mDefaultDBState->corruptFlag) {
  1425   case DBState::OK: {
  1426     // Move to 'closing' state.
  1427     mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
  1429     // Cancel any pending read and close the database. If we do have an
  1430     // in-flight read we want to throw away all the results so far -- we have no
  1431     // idea how consistent the database is. Note that we may have already
  1432     // canceled the read but not emptied our readSet; do so now.
  1433     mDefaultDBState->readSet.Clear();
  1434     if (mDefaultDBState->pendingRead) {
  1435       CancelAsyncRead(true);
  1436       mDefaultDBState->syncConn = nullptr;
  1439     CleanupCachedStatements();
  1440     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1441     CleanupDefaultDBConnection();
  1442     break;
  1444   case DBState::CLOSING_FOR_REBUILD: {
  1445     // We had an error while waiting for close completion. That's OK, just
  1446     // ignore it -- we're rebuilding anyway.
  1447     return;
  1449   case DBState::REBUILDING: {
  1450     // We had an error while rebuilding the DB. Game over. Close the database
  1451     // and let the close handler do nothing; then we'll move it out of the way.
  1452     CleanupCachedStatements();
  1453     if (mDefaultDBState->dbConn) {
  1454       mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
  1456     CleanupDefaultDBConnection();
  1457     break;
  1462 static PLDHashOperator
  1463 RebuildDBCallback(nsCookieEntry *aEntry,
  1464                   void          *aArg)
  1466   mozIStorageBindingParamsArray* paramsArray =
  1467     static_cast<mozIStorageBindingParamsArray*>(aArg);
  1469   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1470   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1471     nsCookie* cookie = cookies[i];
  1473     if (!cookie->IsSession()) {
  1474       bindCookieParameters(paramsArray, aEntry, cookie);
  1478   return PL_DHASH_NEXT;
  1481 void
  1482 nsCookieService::RebuildCorruptDB(DBState* aDBState)
  1484   NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
  1485   NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
  1486     "should be in CLOSING_FOR_REBUILD state");
  1488   aDBState->corruptFlag = DBState::REBUILDING;
  1490   if (mDefaultDBState != aDBState) {
  1491     // We've either closed the state or we've switched profiles. It's getting
  1492     // a bit late to rebuild -- bail instead. In any case, we were waiting
  1493     // on rebuild completion to notify of the db closure, which won't happen --
  1494     // do so now.
  1495     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1496       ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
  1497     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1498     return;
  1501   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1502     ("RebuildCorruptDB(): creating new database"));
  1504   // The database has been closed, and we're ready to rebuild. Open a
  1505   // connection.
  1506   OpenDBResult result = TryInitDB(true);
  1507   if (result != RESULT_OK) {
  1508     // We're done. Reset our DB connection and statements, and notify of
  1509     // closure.
  1510     COOKIE_LOGSTRING(PR_LOG_WARNING,
  1511       ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
  1512     CleanupCachedStatements();
  1513     CleanupDefaultDBConnection();
  1514     mDefaultDBState->corruptFlag = DBState::OK;
  1515     mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
  1516     return;
  1519   // Notify observers that we're beginning the rebuild.
  1520   mObserverService->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
  1522   // Enumerate the hash, and add cookies to the params array.
  1523   mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
  1524   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  1525   stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  1526   aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get());
  1528   // Make sure we've got something to write. If we don't, we're done.
  1529   uint32_t length;
  1530   paramsArray->GetLength(&length);
  1531   if (length == 0) {
  1532     COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1533       ("RebuildCorruptDB(): nothing to write, rebuild complete"));
  1534     mDefaultDBState->corruptFlag = DBState::OK;
  1535     return;
  1538   // Execute the statement. If any errors crop up, we won't try again.
  1539   DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  1540   NS_ASSERT_SUCCESS(rv);
  1541   nsCOMPtr<mozIStoragePendingStatement> handle;
  1542   rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
  1543   NS_ASSERT_SUCCESS(rv);
  1546 nsCookieService::~nsCookieService()
  1548   CloseDBStates();
  1550   UnregisterWeakMemoryReporter(this);
  1552   gCookieService = nullptr;
  1555 NS_IMETHODIMP
  1556 nsCookieService::Observe(nsISupports     *aSubject,
  1557                          const char      *aTopic,
  1558                          const char16_t *aData)
  1560   // check the topic
  1561   if (!strcmp(aTopic, "profile-before-change")) {
  1562     // The profile is about to change,
  1563     // or is going away because the application is shutting down.
  1564     if (mDBState && mDBState->dbConn &&
  1565         !nsCRT::strcmp(aData, MOZ_UTF16("shutdown-cleanse"))) {
  1566       // Clear the cookie db if we're in the default DBState.
  1567       RemoveAll();
  1570     // Close the default DB connection and null out our DBStates before
  1571     // changing.
  1572     CloseDBStates();
  1574   } else if (!strcmp(aTopic, "profile-do-change")) {
  1575     NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
  1576     NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
  1578     // the profile has already changed; init the db from the new location.
  1579     // if we are in the private browsing state, however, we do not want to read
  1580     // data into it - we should instead put it into the default state, so it's
  1581     // ready for us if and when we switch back to it.
  1582     InitDBStates();
  1584   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
  1585     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
  1586     if (prefBranch)
  1587       PrefChanged(prefBranch);
  1589   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
  1590     // Flush all the cookies stored by private browsing contexts
  1591     mPrivateDBState = new DBState();
  1595   return NS_OK;
  1598 NS_IMETHODIMP
  1599 nsCookieService::GetCookieString(nsIURI     *aHostURI,
  1600                                  nsIChannel *aChannel,
  1601                                  char       **aCookie)
  1603   return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
  1606 NS_IMETHODIMP
  1607 nsCookieService::GetCookieStringFromHttp(nsIURI     *aHostURI,
  1608                                          nsIURI     *aFirstURI,
  1609                                          nsIChannel *aChannel,
  1610                                          char       **aCookie)
  1612   return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
  1615 nsresult
  1616 nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
  1617                                        nsIChannel *aChannel,
  1618                                        bool aHttpBound,
  1619                                        char** aCookie)
  1621   NS_ENSURE_ARG(aHostURI);
  1622   NS_ENSURE_ARG(aCookie);
  1624   // Determine whether the request is foreign. Failure is acceptable.
  1625   bool isForeign = true;
  1626   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1628   // Get app info, if channel is present.  Else assume default namespace.
  1629   uint32_t appId = NECKO_NO_APP_ID;
  1630   bool inBrowserElement = false;
  1631   if (aChannel) {
  1632     NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1635   bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1637   nsAutoCString result;
  1638   GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
  1639                           inBrowserElement, isPrivate, result);
  1640   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
  1641   return NS_OK;
  1644 NS_IMETHODIMP
  1645 nsCookieService::SetCookieString(nsIURI     *aHostURI,
  1646                                  nsIPrompt  *aPrompt,
  1647                                  const char *aCookieHeader,
  1648                                  nsIChannel *aChannel)
  1650   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1651   // code that uses this argument by warning if the value is non-null.
  1652   MOZ_ASSERT(!aPrompt);
  1653   if (aPrompt) {
  1654     nsCOMPtr<nsIConsoleService> aConsoleService =
  1655         do_GetService("@mozilla.org/consoleservice;1");
  1656     if (aConsoleService) {
  1657       aConsoleService->LogStringMessage(
  1658         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1661   return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
  1662                                false);
  1665 NS_IMETHODIMP
  1666 nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
  1667                                          nsIURI     *aFirstURI,
  1668                                          nsIPrompt  *aPrompt,
  1669                                          const char *aCookieHeader,
  1670                                          const char *aServerTime,
  1671                                          nsIChannel *aChannel) 
  1673   // The aPrompt argument is deprecated and unused.  Avoid introducing new
  1674   // code that uses this argument by warning if the value is non-null.
  1675   MOZ_ASSERT(!aPrompt);
  1676   if (aPrompt) {
  1677     nsCOMPtr<nsIConsoleService> aConsoleService =
  1678         do_GetService("@mozilla.org/consoleservice;1");
  1679     if (aConsoleService) {
  1680       aConsoleService->LogStringMessage(
  1681         MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
  1684   return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
  1685                                true);
  1688 nsresult
  1689 nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
  1690                                        const char *aCookieHeader,
  1691                                        const char *aServerTime,
  1692                                        nsIChannel *aChannel,
  1693                                        bool aFromHttp) 
  1695   NS_ENSURE_ARG(aHostURI);
  1696   NS_ENSURE_ARG(aCookieHeader);
  1698   // Determine whether the request is foreign. Failure is acceptable.
  1699   bool isForeign = true;
  1700   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
  1702   // Get app info, if channel is present.  Else assume default namespace.
  1703   uint32_t appId = NECKO_NO_APP_ID;
  1704   bool inBrowserElement = false;
  1705   if (aChannel) {
  1706     NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
  1709   bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
  1711   nsDependentCString cookieString(aCookieHeader);
  1712   nsDependentCString serverTime(aServerTime ? aServerTime : "");
  1713   SetCookieStringInternal(aHostURI, isForeign, cookieString,
  1714                           serverTime, aFromHttp, appId, inBrowserElement,
  1715                           isPrivate, aChannel);
  1716   return NS_OK;
  1719 void
  1720 nsCookieService::SetCookieStringInternal(nsIURI             *aHostURI,
  1721                                          bool                aIsForeign,
  1722                                          nsDependentCString &aCookieHeader,
  1723                                          const nsCString    &aServerTime,
  1724                                          bool                aFromHttp,
  1725                                          uint32_t            aAppId,
  1726                                          bool                aInBrowserElement,
  1727                                          bool                aIsPrivate,
  1728                                          nsIChannel         *aChannel)
  1730   NS_ASSERTION(aHostURI, "null host!");
  1732   if (!mDBState) {
  1733     NS_WARNING("No DBState! Profile already closed?");
  1734     return;
  1737   AutoRestore<DBState*> savePrevDBState(mDBState);
  1738   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  1740   // get the base domain for the host URI.
  1741   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  1742   // file:// URI's (i.e. with an empty host) are allowed, but any other
  1743   // scheme must have a non-empty host. A trailing dot in the host
  1744   // is acceptable.
  1745   bool requireHostMatch;
  1746   nsAutoCString baseDomain;
  1747   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  1748   if (NS_FAILED(rv)) {
  1749     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, 
  1750                       "couldn't get base domain from URI");
  1751     return;
  1754   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  1756   // check default prefs
  1757   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  1758                                          aCookieHeader.get());
  1759   // fire a notification if third party or if cookie was rejected
  1760   // (but not if there was an error)
  1761   switch (cookieStatus) {
  1762   case STATUS_REJECTED:
  1763     NotifyRejected(aHostURI);
  1764     if (aIsForeign) {
  1765       NotifyThirdParty(aHostURI, false, aChannel);
  1767     return; // Stop here
  1768   case STATUS_REJECTED_WITH_ERROR:
  1769     return;
  1770   case STATUS_ACCEPTED: // Fallthrough
  1771   case STATUS_ACCEPT_SESSION:
  1772     if (aIsForeign) {
  1773       NotifyThirdParty(aHostURI, true, aChannel);
  1775     break;
  1776   default:
  1777     break;
  1780   // parse server local time. this is not just done here for efficiency
  1781   // reasons - if there's an error parsing it, and we need to default it
  1782   // to the current time, we must do it here since the current time in
  1783   // SetCookieInternal() will change for each cookie processed (e.g. if the
  1784   // user is prompted).
  1785   PRTime tempServerTime;
  1786   int64_t serverTime;
  1787   PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
  1788                                        &tempServerTime);
  1789   if (result == PR_SUCCESS) {
  1790     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
  1791   } else {
  1792     serverTime = PR_Now() / PR_USEC_PER_SEC;
  1795   // process each cookie in the header
  1796   while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
  1797                            aCookieHeader, serverTime, aFromHttp, aChannel)) {
  1798     // document.cookie can only set one cookie at a time
  1799     if (!aFromHttp)
  1800       break;
  1804 // notify observers that a cookie was rejected due to the users' prefs.
  1805 void
  1806 nsCookieService::NotifyRejected(nsIURI *aHostURI)
  1808   if (mObserverService) {
  1809     mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
  1813 // notify observers that a third-party cookie was accepted/rejected
  1814 // if the cookie issuer is unknown, it defaults to "?"
  1815 void
  1816 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
  1818   if (!mObserverService) {
  1819     return;
  1822   const char* topic;
  1824   if (mDBState != mPrivateDBState) {
  1825     // Regular (non-private) browsing
  1826     if (aIsAccepted) {
  1827       topic = "third-party-cookie-accepted";
  1828     } else {
  1829       topic = "third-party-cookie-rejected";
  1831   } else {
  1832     // Private browsing
  1833     if (aIsAccepted) {
  1834       topic = "private-third-party-cookie-accepted";
  1835     } else {
  1836       topic = "private-third-party-cookie-rejected";
  1840   do {
  1841     // Attempt to find the host of aChannel.
  1842     if (!aChannel) {
  1843       break;
  1845     nsCOMPtr<nsIURI> channelURI;
  1846     nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
  1847     if (NS_FAILED(rv)) {
  1848       break;
  1851     nsAutoCString referringHost;
  1852     rv = channelURI->GetHost(referringHost);
  1853     if (NS_FAILED(rv)) {
  1854       break;
  1857     nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
  1858     mObserverService->NotifyObservers(aHostURI,
  1859                                       topic,
  1860                                       referringHostUTF16.get());
  1861     return;
  1862   } while (0);
  1864   // This can fail for a number of reasons, in which kind we fallback to "?"
  1865   mObserverService->NotifyObservers(aHostURI,
  1866                                     topic,
  1867                                     MOZ_UTF16("?"));
  1870 // notify observers that the cookie list changed. there are five possible
  1871 // values for aData:
  1872 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
  1873 // "added"   means a cookie was added. aSubject is the added cookie.
  1874 // "changed" means a cookie was altered. aSubject is the new cookie.
  1875 // "cleared" means the entire cookie list was cleared. aSubject is null.
  1876 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
  1877 // cookies.
  1878 void
  1879 nsCookieService::NotifyChanged(nsISupports     *aSubject,
  1880                                const char16_t *aData)
  1882   const char* topic = mDBState == mPrivateDBState ?
  1883       "private-cookie-changed" : "cookie-changed";
  1884   if (mObserverService)
  1885     mObserverService->NotifyObservers(aSubject, topic, aData);
  1888 already_AddRefed<nsIArray>
  1889 nsCookieService::CreatePurgeList(nsICookie2* aCookie)
  1891   nsCOMPtr<nsIMutableArray> removedList =
  1892     do_CreateInstance(NS_ARRAY_CONTRACTID);
  1893   removedList->AppendElement(aCookie, false);
  1894   return removedList.forget();
  1897 /******************************************************************************
  1898  * nsCookieService:
  1899  * pref observer impl
  1900  ******************************************************************************/
  1902 void
  1903 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
  1905   int32_t val;
  1906   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
  1907     mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
  1909   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
  1910     mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
  1912   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
  1913     mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
  1915   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
  1916     mCookiePurgeAge =
  1917       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
  1920   bool boolval;
  1921   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
  1922     mThirdPartySession = boolval;
  1925 /******************************************************************************
  1926  * nsICookieManager impl:
  1927  * nsICookieManager
  1928  ******************************************************************************/
  1930 NS_IMETHODIMP
  1931 nsCookieService::RemoveAll()
  1933   if (!mDBState) {
  1934     NS_WARNING("No DBState! Profile already closed?");
  1935     return NS_ERROR_NOT_AVAILABLE;
  1938   RemoveAllFromMemory();
  1940   // clear the cookie file
  1941   if (mDBState->dbConn) {
  1942     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
  1944     // Cancel any pending read. No further results will be received by our
  1945     // read listener.
  1946     if (mDefaultDBState->pendingRead) {
  1947       CancelAsyncRead(true);
  1950     nsCOMPtr<mozIStorageAsyncStatement> stmt;
  1951     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1952       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
  1953     if (NS_SUCCEEDED(rv)) {
  1954       nsCOMPtr<mozIStoragePendingStatement> handle;
  1955       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
  1956         getter_AddRefs(handle));
  1957       NS_ASSERT_SUCCESS(rv);
  1958     } else {
  1959       // Recreate the database.
  1960       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  1961         ("RemoveAll(): corruption detected with rv 0x%x", rv));
  1962       HandleCorruptDB(mDefaultDBState);
  1966   NotifyChanged(nullptr, MOZ_UTF16("cleared"));
  1967   return NS_OK;
  1970 static PLDHashOperator
  1971 COMArrayCallback(nsCookieEntry *aEntry,
  1972                  void          *aArg)
  1974   nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
  1976   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  1977   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  1978     data->AppendObject(cookies[i]);
  1981   return PL_DHASH_NEXT;
  1984 NS_IMETHODIMP
  1985 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
  1987   if (!mDBState) {
  1988     NS_WARNING("No DBState! Profile already closed?");
  1989     return NS_ERROR_NOT_AVAILABLE;
  1992   EnsureReadComplete();
  1994   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
  1995   mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
  1997   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  2000 NS_IMETHODIMP
  2001 nsCookieService::Add(const nsACString &aHost,
  2002                      const nsACString &aPath,
  2003                      const nsACString &aName,
  2004                      const nsACString &aValue,
  2005                      bool              aIsSecure,
  2006                      bool              aIsHttpOnly,
  2007                      bool              aIsSession,
  2008                      int64_t           aExpiry)
  2010   if (!mDBState) {
  2011     NS_WARNING("No DBState! Profile already closed?");
  2012     return NS_ERROR_NOT_AVAILABLE;
  2015   // first, normalize the hostname, and fail if it contains illegal characters.
  2016   nsAutoCString host(aHost);
  2017   nsresult rv = NormalizeHost(host);
  2018   NS_ENSURE_SUCCESS(rv, rv);
  2020   // get the base domain for the host URI.
  2021   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
  2022   nsAutoCString baseDomain;
  2023   rv = GetBaseDomainFromHost(host, baseDomain);
  2024   NS_ENSURE_SUCCESS(rv, rv);
  2026   int64_t currentTimeInUsec = PR_Now();
  2028   nsRefPtr<nsCookie> cookie =
  2029     nsCookie::Create(aName, aValue, host, aPath,
  2030                      aExpiry,
  2031                      currentTimeInUsec,
  2032                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2033                      aIsSession,
  2034                      aIsSecure,
  2035                      aIsHttpOnly);
  2036   if (!cookie) {
  2037     return NS_ERROR_OUT_OF_MEMORY;
  2040   AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
  2041   return NS_OK;
  2045 nsresult
  2046 nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId,
  2047                         bool aInBrowserElement, const nsACString& aName,
  2048                         const nsACString& aPath, bool aBlocked)
  2050   if (!mDBState) {
  2051     NS_WARNING("No DBState! Profile already closed?");
  2052     return NS_ERROR_NOT_AVAILABLE;
  2055   // first, normalize the hostname, and fail if it contains illegal characters.
  2056   nsAutoCString host(aHost);
  2057   nsresult rv = NormalizeHost(host);
  2058   NS_ENSURE_SUCCESS(rv, rv);
  2060   nsAutoCString baseDomain;
  2061   rv = GetBaseDomainFromHost(host, baseDomain);
  2062   NS_ENSURE_SUCCESS(rv, rv);
  2064   nsListIter matchIter;
  2065   nsRefPtr<nsCookie> cookie;
  2066   if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement),
  2067                  host,
  2068                  PromiseFlatCString(aName),
  2069                  PromiseFlatCString(aPath),
  2070                  matchIter)) {
  2071     cookie = matchIter.Cookie();
  2072     RemoveCookieFromList(matchIter);
  2075   // check if we need to add the host to the permissions blacklist.
  2076   if (aBlocked && mPermissionService) {
  2077     // strip off the domain dot, if necessary
  2078     if (!host.IsEmpty() && host.First() == '.')
  2079       host.Cut(0, 1);
  2081     host.Insert(NS_LITERAL_CSTRING("http://"), 0);
  2083     nsCOMPtr<nsIURI> uri;
  2084     NS_NewURI(getter_AddRefs(uri), host);
  2086     if (uri)
  2087       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
  2090   if (cookie) {
  2091     // Everything's done. Notify observers.
  2092     NotifyChanged(cookie, MOZ_UTF16("deleted"));
  2095   return NS_OK;
  2098 NS_IMETHODIMP
  2099 nsCookieService::Remove(const nsACString &aHost,
  2100                         const nsACString &aName,
  2101                         const nsACString &aPath,
  2102                         bool             aBlocked)
  2104   return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked);
  2107 /******************************************************************************
  2108  * nsCookieService impl:
  2109  * private file I/O functions
  2110  ******************************************************************************/
  2112 // Begin an asynchronous read from the database.
  2113 OpenDBResult
  2114 nsCookieService::Read()
  2116   // Set up a statement for the read. Note that our query specifies that
  2117   // 'baseDomain' not be nullptr -- see below for why.
  2118   nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
  2119   nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2120     "SELECT "
  2121       "name, "
  2122       "value, "
  2123       "host, "
  2124       "path, "
  2125       "expiry, "
  2126       "lastAccessed, "
  2127       "creationTime, "
  2128       "isSecure, "
  2129       "isHttpOnly, "
  2130       "baseDomain, "
  2131       "appId,  "
  2132       "inBrowserElement "
  2133     "FROM moz_cookies "
  2134     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
  2135   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2137   // Set up a statement to delete any rows with a nullptr 'baseDomain'
  2138   // column. This takes care of any cookies set by browsers that don't
  2139   // understand the 'baseDomain' column, where the database schema version
  2140   // is from one that does. (This would occur when downgrading.)
  2141   nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
  2142   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2143     "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
  2144     getter_AddRefs(stmtDeleteNull));
  2145   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2147   // Start a new connection for sync reads, to reduce contention with the
  2148   // background thread. We need to do this before we kick off write statements,
  2149   // since they can lock the database and prevent connections from being opened.
  2150   rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
  2151     getter_AddRefs(mDefaultDBState->syncConn));
  2152   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
  2154   // Init our readSet hash and execute the statements. Note that, after this
  2155   // point, we cannot fail without altering the cleanup code in InitDBStates()
  2156   // to handle closing of the now-asynchronous connection.
  2157   mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
  2159   mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
  2160   rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
  2161     getter_AddRefs(mDefaultDBState->pendingRead));
  2162   NS_ASSERT_SUCCESS(rv);
  2164   nsCOMPtr<mozIStoragePendingStatement> handle;
  2165   rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
  2166     getter_AddRefs(handle));
  2167   NS_ASSERT_SUCCESS(rv);
  2169   return RESULT_OK;
  2172 // Extract data from a single result row and create an nsCookie.
  2173 // This is templated since 'T' is different for sync vs async results.
  2174 template<class T> nsCookie*
  2175 nsCookieService::GetCookieFromRow(T &aRow)
  2177   // Skip reading 'baseDomain' -- up to the caller.
  2178   nsCString name, value, host, path;
  2179   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
  2180   NS_ASSERT_SUCCESS(rv);
  2181   rv = aRow->GetUTF8String(IDX_VALUE, value);
  2182   NS_ASSERT_SUCCESS(rv);
  2183   rv = aRow->GetUTF8String(IDX_HOST, host);
  2184   NS_ASSERT_SUCCESS(rv);
  2185   rv = aRow->GetUTF8String(IDX_PATH, path);
  2186   NS_ASSERT_SUCCESS(rv);
  2188   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
  2189   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
  2190   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
  2191   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
  2192   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
  2194   // Create a new nsCookie and assign the data.
  2195   return nsCookie::Create(name, value, host, path,
  2196                           expiry,
  2197                           lastAccessed,
  2198                           creationTime,
  2199                           false,
  2200                           isSecure,
  2201                           isHttpOnly);
  2204 void
  2205 nsCookieService::AsyncReadComplete()
  2207   // We may be in the private browsing DB state, with a pending read on the
  2208   // default DB state. (This would occur if we started up in private browsing
  2209   // mode.) As long as we do all our operations on the default state, we're OK.
  2210   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2211   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2212   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2214   // Merge the data read on the background thread with the data synchronously
  2215   // read on the main thread. Note that transactions on the cookie table may
  2216   // have occurred on the main thread since, making the background data stale.
  2217   for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
  2218     const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
  2220     // Tiebreak: if the given base domain has already been read in, ignore
  2221     // the background data. Note that readSet may contain domains that were
  2222     // queried but found not to be in the db -- that's harmless.
  2223     if (mDefaultDBState->readSet.GetEntry(tuple.key))
  2224       continue;
  2226     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
  2229   mDefaultDBState->stmtReadDomain = nullptr;
  2230   mDefaultDBState->pendingRead = nullptr;
  2231   mDefaultDBState->readListener = nullptr;
  2232   mDefaultDBState->syncConn = nullptr;
  2233   mDefaultDBState->hostArray.Clear();
  2234   mDefaultDBState->readSet.Clear();
  2236   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
  2237                                   mDefaultDBState->cookieCount));
  2239   mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
  2242 void
  2243 nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
  2245   // We may be in the private browsing DB state, with a pending read on the
  2246   // default DB state. (This would occur if we started up in private browsing
  2247   // mode.) As long as we do all our operations on the default state, we're OK.
  2248   NS_ASSERTION(mDefaultDBState, "no default DBState");
  2249   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
  2250   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
  2252   // Cancel the pending read, kill the read listener, and empty the array
  2253   // of data already read in on the background thread.
  2254   mDefaultDBState->readListener->Cancel();
  2255   DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
  2256   NS_ASSERT_SUCCESS(rv);
  2258   mDefaultDBState->stmtReadDomain = nullptr;
  2259   mDefaultDBState->pendingRead = nullptr;
  2260   mDefaultDBState->readListener = nullptr;
  2261   mDefaultDBState->hostArray.Clear();
  2263   // Only clear the 'readSet' table if we no longer need to know what set of
  2264   // data is already accounted for.
  2265   if (aPurgeReadSet)
  2266     mDefaultDBState->readSet.Clear();
  2269 void
  2270 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
  2272   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2273     "not in default db state");
  2275   // Fast path 1: nothing to read, or we've already finished reading.
  2276   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2277     return;
  2279   // Fast path 2: already read in this particular domain.
  2280   if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
  2281     return;
  2283   // Read in the data synchronously.
  2284   // see IDX_NAME, etc. for parameter indexes
  2285   nsresult rv;
  2286   if (!mDefaultDBState->stmtReadDomain) {
  2287     // Cache the statement, since it's likely to be used again.
  2288     rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2289       "SELECT "
  2290         "name, "
  2291         "value, "
  2292         "host, "
  2293         "path, "
  2294         "expiry, "
  2295         "lastAccessed, "
  2296         "creationTime, "
  2297         "isSecure, "
  2298         "isHttpOnly "
  2299       "FROM moz_cookies "
  2300       "WHERE baseDomain = :baseDomain "
  2301       "  AND appId = :appId "
  2302       "  AND inBrowserElement = :inBrowserElement"),
  2303       getter_AddRefs(mDefaultDBState->stmtReadDomain));
  2305     if (NS_FAILED(rv)) {
  2306       // Recreate the database.
  2307       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2308         ("EnsureReadDomain(): corruption detected when creating statement "
  2309          "with rv 0x%x", rv));
  2310       HandleCorruptDB(mDefaultDBState);
  2311       return;
  2315   NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
  2317   mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
  2319   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
  2320     NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
  2321   NS_ASSERT_SUCCESS(rv);
  2322   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2323     NS_LITERAL_CSTRING("appId"), aKey.mAppId);
  2324   NS_ASSERT_SUCCESS(rv);
  2325   rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
  2326     NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
  2327   NS_ASSERT_SUCCESS(rv);
  2330   bool hasResult;
  2331   nsCString name, value, host, path;
  2332   nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
  2333   while (1) {
  2334     rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
  2335     if (NS_FAILED(rv)) {
  2336       // Recreate the database.
  2337       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2338         ("EnsureReadDomain(): corruption detected when reading result "
  2339          "with rv 0x%x", rv));
  2340       HandleCorruptDB(mDefaultDBState);
  2341       return;
  2344     if (!hasResult)
  2345       break;
  2347     array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
  2350   // Add the cookies to the table in a single operation. This makes sure that
  2351   // either all the cookies get added, or in the case of corruption, none.
  2352   for (uint32_t i = 0; i < array.Length(); ++i) {
  2353     AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
  2356   // Add it to the hashset of read entries, so we don't read it again.
  2357   mDefaultDBState->readSet.PutEntry(aKey);
  2359   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2360     ("EnsureReadDomain(): %ld cookies read for base domain %s, "
  2361      " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
  2362      (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
  2365 void
  2366 nsCookieService::EnsureReadComplete()
  2368   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
  2369     "not in default db state");
  2371   // Fast path 1: nothing to read, or we've already finished reading.
  2372   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
  2373     return;
  2375   // Cancel the pending read, so we don't get any more results.
  2376   CancelAsyncRead(false);
  2378   // Read in the data synchronously.
  2379   // see IDX_NAME, etc. for parameter indexes
  2380   nsCOMPtr<mozIStorageStatement> stmt;
  2381   nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
  2382     "SELECT "
  2383       "name, "
  2384       "value, "
  2385       "host, "
  2386       "path, "
  2387       "expiry, "
  2388       "lastAccessed, "
  2389       "creationTime, "
  2390       "isSecure, "
  2391       "isHttpOnly, "
  2392       "baseDomain, "
  2393       "appId,  "
  2394       "inBrowserElement "
  2395     "FROM moz_cookies "
  2396     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
  2398   if (NS_FAILED(rv)) {
  2399     // Recreate the database.
  2400     COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2401       ("EnsureReadComplete(): corruption detected when creating statement "
  2402        "with rv 0x%x", rv));
  2403     HandleCorruptDB(mDefaultDBState);
  2404     return;
  2407   nsCString baseDomain, name, value, host, path;
  2408   uint32_t appId;
  2409   bool inBrowserElement, hasResult;
  2410   nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
  2411   while (1) {
  2412     rv = stmt->ExecuteStep(&hasResult);
  2413     if (NS_FAILED(rv)) {
  2414       // Recreate the database.
  2415       COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2416         ("EnsureReadComplete(): corruption detected when reading result "
  2417          "with rv 0x%x", rv));
  2418       HandleCorruptDB(mDefaultDBState);
  2419       return;
  2422     if (!hasResult)
  2423       break;
  2425     // Make sure we haven't already read the data.
  2426     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
  2427     appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID));
  2428     inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM));
  2429     nsCookieKey key(baseDomain, appId, inBrowserElement);
  2430     if (mDefaultDBState->readSet.GetEntry(key))
  2431       continue;
  2433     CookieDomainTuple* tuple = array.AppendElement();
  2434     tuple->key = key;
  2435     tuple->cookie = GetCookieFromRow(stmt);
  2438   // Add the cookies to the table in a single operation. This makes sure that
  2439   // either all the cookies get added, or in the case of corruption, none.
  2440   for (uint32_t i = 0; i < array.Length(); ++i) {
  2441     CookieDomainTuple& tuple = array[i];
  2442     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
  2443       false);
  2446   mDefaultDBState->syncConn = nullptr;
  2447   mDefaultDBState->readSet.Clear();
  2449   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  2450     ("EnsureReadComplete(): %ld cookies read", array.Length()));
  2453 NS_IMETHODIMP
  2454 nsCookieService::ImportCookies(nsIFile *aCookieFile)
  2456   if (!mDBState) {
  2457     NS_WARNING("No DBState! Profile already closed?");
  2458     return NS_ERROR_NOT_AVAILABLE;
  2461   // Make sure we're in the default DB state. We don't want people importing
  2462   // cookies into a private browsing session!
  2463   if (mDBState != mDefaultDBState) {
  2464     NS_WARNING("Trying to import cookies in a private browsing session!");
  2465     return NS_ERROR_NOT_AVAILABLE;
  2468   nsresult rv;
  2469   nsCOMPtr<nsIInputStream> fileInputStream;
  2470   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
  2471   if (NS_FAILED(rv)) return rv;
  2473   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
  2474   if (NS_FAILED(rv)) return rv;
  2476   // First, ensure we've read in everything from the database, if we have one.
  2477   EnsureReadComplete();
  2479   static const char kTrue[] = "TRUE";
  2481   nsAutoCString buffer, baseDomain;
  2482   bool isMore = true;
  2483   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
  2484   nsASingleFragmentCString::char_iterator iter;
  2485   int32_t numInts;
  2486   int64_t expires;
  2487   bool isDomain, isHttpOnly = false;
  2488   uint32_t originalCookieCount = mDefaultDBState->cookieCount;
  2490   int64_t currentTimeInUsec = PR_Now();
  2491   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2492   // we use lastAccessedCounter to keep cookies in recently-used order,
  2493   // so we start by initializing to currentTime (somewhat arbitrary)
  2494   int64_t lastAccessedCounter = currentTimeInUsec;
  2496   /* file format is:
  2498    * host \t isDomain \t path \t secure \t expires \t name \t cookie
  2500    * if this format isn't respected we move onto the next line in the file.
  2501    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
  2502    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
  2503    * expires is a int64_t integer
  2504    * note 1: cookie can contain tabs.
  2505    * note 2: cookies will be stored in order of lastAccessed time:
  2506    *         most-recently used come first; least-recently-used come last.
  2507    */
  2509   /*
  2510    * ...but due to bug 178933, we hide HttpOnly cookies from older code
  2511    * in a comment, so they don't expose HttpOnly cookies to JS.
  2513    * The format for HttpOnly cookies is
  2515    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
  2517    */
  2519   // We will likely be adding a bunch of cookies to the DB, so we use async
  2520   // batching with storage to make this super fast.
  2521   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2522   if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
  2523     mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2526   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
  2527     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
  2528       isHttpOnly = true;
  2529       hostIndex = sizeof(kHttpOnlyPrefix) - 1;
  2530     } else if (buffer.IsEmpty() || buffer.First() == '#') {
  2531       continue;
  2532     } else {
  2533       isHttpOnly = false;
  2534       hostIndex = 0;
  2537     // this is a cheap, cheesy way of parsing a tab-delimited line into
  2538     // string indexes, which can be lopped off into substrings. just for
  2539     // purposes of obfuscation, it also checks that each token was found.
  2540     // todo: use iterators?
  2541     if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
  2542         (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
  2543         (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
  2544         (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
  2545         (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
  2546         (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
  2547       continue;
  2550     // check the expirytime first - if it's expired, ignore
  2551     // nullstomp the trailing tab, to avoid copying the string
  2552     buffer.BeginWriting(iter);
  2553     *(iter += nameIndex - 1) = char(0);
  2554     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
  2555     if (numInts != 1 || expires < currentTime) {
  2556       continue;
  2559     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
  2560     const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
  2561     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
  2562     // and discard
  2563     if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
  2564         host.FindChar(':') != kNotFound) {
  2565       continue;
  2568     // compute the baseDomain from the host
  2569     rv = GetBaseDomainFromHost(host, baseDomain);
  2570     if (NS_FAILED(rv))
  2571       continue;
  2573     // pre-existing cookies have appId=0, inBrowser=false
  2574     nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  2576     // Create a new nsCookie and assign the data. We don't know the cookie
  2577     // creation time, so just use the current time to generate a unique one.
  2578     nsRefPtr<nsCookie> newCookie =
  2579       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
  2580                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
  2581                        host,
  2582                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
  2583                        expires,
  2584                        lastAccessedCounter,
  2585                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2586                        false,
  2587                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
  2588                        isHttpOnly);
  2589     if (!newCookie) {
  2590       return NS_ERROR_OUT_OF_MEMORY;
  2593     // trick: preserve the most-recently-used cookie ordering,
  2594     // by successively decrementing the lastAccessed time
  2595     lastAccessedCounter--;
  2597     if (originalCookieCount == 0) {
  2598       AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
  2600     else {
  2601       AddInternal(key, newCookie, currentTimeInUsec,
  2602                   nullptr, nullptr, true);
  2606   // If we need to write to disk, do so now.
  2607   if (paramsArray) {
  2608     uint32_t length;
  2609     paramsArray->GetLength(&length);
  2610     if (length) {
  2611       rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
  2612       NS_ASSERT_SUCCESS(rv);
  2613       nsCOMPtr<mozIStoragePendingStatement> handle;
  2614       rv = mDefaultDBState->stmtInsert->ExecuteAsync(
  2615         mDefaultDBState->insertListener, getter_AddRefs(handle));
  2616       NS_ASSERT_SUCCESS(rv);
  2621   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
  2622     mDefaultDBState->cookieCount));
  2624   return NS_OK;
  2627 /******************************************************************************
  2628  * nsCookieService impl:
  2629  * private GetCookie/SetCookie helpers
  2630  ******************************************************************************/
  2632 // helper function for GetCookieList
  2633 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
  2635 // Comparator class for sorting cookies before sending to a server.
  2636 class CompareCookiesForSending
  2638 public:
  2639   bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2641     return aCookie1->CreationTime() == aCookie2->CreationTime() &&
  2642            aCookie2->Path().Length() == aCookie1->Path().Length();
  2645   bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
  2647     // compare by cookie path length in accordance with RFC2109
  2648     int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
  2649     if (result != 0)
  2650       return result < 0;
  2652     // when path lengths match, older cookies should be listed first.  this is
  2653     // required for backwards compatibility since some websites erroneously
  2654     // depend on receiving cookies in the order in which they were sent to the
  2655     // browser!  see bug 236772.
  2656     return aCookie1->CreationTime() < aCookie2->CreationTime();
  2658 };
  2660 void
  2661 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
  2662                                          bool aIsForeign,
  2663                                          bool aHttpBound,
  2664                                          uint32_t aAppId,
  2665                                          bool aInBrowserElement,
  2666                                          bool aIsPrivate,
  2667                                          nsCString &aCookieString)
  2669   NS_ASSERTION(aHostURI, "null host!");
  2671   if (!mDBState) {
  2672     NS_WARNING("No DBState! Profile already closed?");
  2673     return;
  2676   AutoRestore<DBState*> savePrevDBState(mDBState);
  2677   mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
  2679   // get the base domain, host, and path from the URI.
  2680   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
  2681   // file:// URI's (i.e. with an empty host) are allowed, but any other
  2682   // scheme must have a non-empty host. A trailing dot in the host
  2683   // is acceptable.
  2684   bool requireHostMatch;
  2685   nsAutoCString baseDomain, hostFromURI, pathFromURI;
  2686   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
  2687   if (NS_SUCCEEDED(rv))
  2688     rv = aHostURI->GetAsciiHost(hostFromURI);
  2689   if (NS_SUCCEEDED(rv))
  2690     rv = aHostURI->GetPath(pathFromURI);
  2691   if (NS_FAILED(rv)) {
  2692     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
  2693     return;
  2696   // check default prefs
  2697   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
  2698                                          nullptr);
  2699   // for GetCookie(), we don't fire rejection notifications.
  2700   switch (cookieStatus) {
  2701   case STATUS_REJECTED:
  2702   case STATUS_REJECTED_WITH_ERROR:
  2703     return;
  2704   default:
  2705     break;
  2708   // check if aHostURI is using an https secure protocol.
  2709   // if it isn't, then we can't send a secure cookie over the connection.
  2710   // if SchemeIs fails, assume an insecure connection, to be on the safe side
  2711   bool isSecure;
  2712   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
  2713     isSecure = false;
  2716   nsCookie *cookie;
  2717   nsAutoTArray<nsCookie*, 8> foundCookieList;
  2718   int64_t currentTimeInUsec = PR_Now();
  2719   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
  2720   bool stale = false;
  2722   nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
  2723   EnsureReadDomain(key);
  2725   // perform the hash lookup
  2726   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  2727   if (!entry)
  2728     return;
  2730   // iterate the cookies!
  2731   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  2732   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  2733     cookie = cookies[i];
  2735     // check the host, since the base domain lookup is conservative.
  2736     // first, check for an exact host or domain cookie match, e.g. "google.com"
  2737     // or ".google.com"; second a subdomain match, e.g.
  2738     // host = "mail.google.com", cookie domain = ".google.com".
  2739     if (cookie->RawHost() != hostFromURI &&
  2740         !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
  2741       continue;
  2743     // if the cookie is secure and the host scheme isn't, we can't send it
  2744     if (cookie->IsSecure() && !isSecure)
  2745       continue;
  2747     // if the cookie is httpOnly and it's not going directly to the HTTP
  2748     // connection, don't send it
  2749     if (cookie->IsHttpOnly() && !aHttpBound)
  2750       continue;
  2752     // calculate cookie path length, excluding trailing '/'
  2753     uint32_t cookiePathLen = cookie->Path().Length();
  2754     if (cookiePathLen > 0 && cookie->Path().Last() == '/')
  2755       --cookiePathLen;
  2757     // if the nsIURI path is shorter than the cookie path, don't send it back
  2758     if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
  2759       continue;
  2761     if (pathFromURI.Length() > cookiePathLen &&
  2762         !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
  2763       /*
  2764        * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
  2765        * '/' is the "standard" case; the '?' test allows a site at host/abc?def
  2766        * to receive a cookie that has a path attribute of abc.  this seems
  2767        * strange but at least one major site (citibank, bug 156725) depends
  2768        * on it.  The test for # and ; are put in to proactively avoid problems
  2769        * with other sites - these are the only other chars allowed in the path.
  2770        */
  2771       continue;
  2774     // check if the cookie has expired
  2775     if (cookie->Expiry() <= currentTime) {
  2776       continue;
  2779     // all checks passed - add to list and check if lastAccessed stamp needs updating
  2780     foundCookieList.AppendElement(cookie);
  2781     if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2782       stale = true;
  2785   int32_t count = foundCookieList.Length();
  2786   if (count == 0)
  2787     return;
  2789   // update lastAccessed timestamps. we only do this if the timestamp is stale
  2790   // by a certain amount, to avoid thrashing the db during pageload.
  2791   if (stale) {
  2792     // Create an array of parameters to bind to our update statement. Batching
  2793     // is OK here since we're updating cookies with no interleaved operations.
  2794     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  2795     mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
  2796     if (mDBState->dbConn) {
  2797       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  2800     for (int32_t i = 0; i < count; ++i) {
  2801       cookie = foundCookieList.ElementAt(i);
  2803       if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
  2804         UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
  2806     // Update the database now if necessary.
  2807     if (paramsArray) {
  2808       uint32_t length;
  2809       paramsArray->GetLength(&length);
  2810       if (length) {
  2811         DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  2812         NS_ASSERT_SUCCESS(rv);
  2813         nsCOMPtr<mozIStoragePendingStatement> handle;
  2814         rv = stmt->ExecuteAsync(mDBState->updateListener,
  2815           getter_AddRefs(handle));
  2816         NS_ASSERT_SUCCESS(rv);
  2821   // return cookies in order of path length; longest to shortest.
  2822   // this is required per RFC2109.  if cookies match in length,
  2823   // then sort by creation time (see bug 236772).
  2824   foundCookieList.Sort(CompareCookiesForSending());
  2826   for (int32_t i = 0; i < count; ++i) {
  2827     cookie = foundCookieList.ElementAt(i);
  2829     // check if we have anything to write
  2830     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
  2831       // if we've already added a cookie to the return list, append a "; " so
  2832       // that subsequent cookies are delimited in the final list.
  2833       if (!aCookieString.IsEmpty()) {
  2834         aCookieString.AppendLiteral("; ");
  2837       if (!cookie->Name().IsEmpty()) {
  2838         // we have a name and value - write both
  2839         aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
  2840       } else {
  2841         // just write value
  2842         aCookieString += cookie->Value();
  2847   if (!aCookieString.IsEmpty())
  2848     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
  2851 // processes a single cookie, and returns true if there are more cookies
  2852 // to be processed
  2853 bool
  2854 nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
  2855                                    const nsCookieKey             &aKey,
  2856                                    bool                           aRequireHostMatch,
  2857                                    CookieStatus                   aStatus,
  2858                                    nsDependentCString            &aCookieHeader,
  2859                                    int64_t                        aServerTime,
  2860                                    bool                           aFromHttp,
  2861                                    nsIChannel                    *aChannel)
  2863   NS_ASSERTION(aHostURI, "null host!");
  2865   // create a stack-based nsCookieAttributes, to store all the
  2866   // attributes parsed from the cookie
  2867   nsCookieAttributes cookieAttributes;
  2869   // init expiryTime such that session cookies won't prematurely expire
  2870   cookieAttributes.expiryTime = INT64_MAX;
  2872   // aCookieHeader is an in/out param to point to the next cookie, if
  2873   // there is one. Save the present value for logging purposes
  2874   nsDependentCString savedCookieHeader(aCookieHeader);
  2876   // newCookie says whether there are multiple cookies in the header;
  2877   // so we can handle them separately.
  2878   bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
  2880   int64_t currentTimeInUsec = PR_Now();
  2882   // calculate expiry time of cookie.
  2883   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
  2884                                          currentTimeInUsec / PR_USEC_PER_SEC);
  2885   if (aStatus == STATUS_ACCEPT_SESSION) {
  2886     // force lifetime to session. note that the expiration time, if set above,
  2887     // will still apply.
  2888     cookieAttributes.isSession = true;
  2891   // reject cookie if it's over the size limit, per RFC2109
  2892   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
  2893     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
  2894     return newCookie;
  2897   if (cookieAttributes.name.FindChar('\t') != kNotFound) {
  2898     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
  2899     return newCookie;
  2902   // domain & path checks
  2903   if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
  2904     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
  2905     return newCookie;
  2907   if (!CheckPath(cookieAttributes, aHostURI)) {
  2908     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
  2909     return newCookie;
  2912   // create a new nsCookie and copy attributes
  2913   nsRefPtr<nsCookie> cookie =
  2914     nsCookie::Create(cookieAttributes.name,
  2915                      cookieAttributes.value,
  2916                      cookieAttributes.host,
  2917                      cookieAttributes.path,
  2918                      cookieAttributes.expiryTime,
  2919                      currentTimeInUsec,
  2920                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
  2921                      cookieAttributes.isSession,
  2922                      cookieAttributes.isSecure,
  2923                      cookieAttributes.isHttpOnly);
  2924   if (!cookie)
  2925     return newCookie;
  2927   // check permissions from site permission list, or ask the user,
  2928   // to determine if we can set the cookie
  2929   if (mPermissionService) {
  2930     bool permission;
  2931     mPermissionService->CanSetCookie(aHostURI,
  2932                                      aChannel,
  2933                                      static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
  2934                                      &cookieAttributes.isSession,
  2935                                      &cookieAttributes.expiryTime,
  2936                                      &permission);
  2937     if (!permission) {
  2938       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
  2939       NotifyRejected(aHostURI);
  2940       return newCookie;
  2943     // update isSession and expiry attributes, in case they changed
  2944     cookie->SetIsSession(cookieAttributes.isSession);
  2945     cookie->SetExpiry(cookieAttributes.expiryTime);
  2948   // add the cookie to the list. AddInternal() takes care of logging.
  2949   // we get the current time again here, since it may have changed during prompting
  2950   AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
  2951               aFromHttp);
  2952   return newCookie;
  2955 // this is a backend function for adding a cookie to the list, via SetCookie.
  2956 // also used in the cookie manager, for profile migration from IE.
  2957 // it either replaces an existing cookie; or adds the cookie to the hashtable,
  2958 // and deletes a cookie (if maximum number of cookies has been
  2959 // reached). also performs list maintenance by removing expired cookies.
  2960 void
  2961 nsCookieService::AddInternal(const nsCookieKey             &aKey,
  2962                              nsCookie                      *aCookie,
  2963                              int64_t                        aCurrentTimeInUsec,
  2964                              nsIURI                        *aHostURI,
  2965                              const char                    *aCookieHeader,
  2966                              bool                           aFromHttp)
  2968   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
  2970   // if the new cookie is httponly, make sure we're not coming from script
  2971   if (!aFromHttp && aCookie->IsHttpOnly()) {
  2972     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  2973       "cookie is httponly; coming from script");
  2974     return;
  2977   nsListIter matchIter;
  2978   bool foundCookie = FindCookie(aKey, aCookie->Host(),
  2979     aCookie->Name(), aCookie->Path(), matchIter);
  2981   nsRefPtr<nsCookie> oldCookie;
  2982   nsCOMPtr<nsIArray> purgedList;
  2983   if (foundCookie) {
  2984     oldCookie = matchIter.Cookie();
  2986     // Check if the old cookie is stale (i.e. has already expired). If so, we
  2987     // need to be careful about the semantics of removing it and adding the new
  2988     // cookie: we want the behavior wrt adding the new cookie to be the same as
  2989     // if it didn't exist, but we still want to fire a removal notification.
  2990     if (oldCookie->Expiry() <= currentTime) {
  2991       if (aCookie->Expiry() <= currentTime) {
  2992         // The new cookie has expired and the old one is stale. Nothing to do.
  2993         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  2994           "cookie has already expired");
  2995         return;
  2998       // Remove the stale cookie. We save notification for later, once all list
  2999       // modifications are complete.
  3000       RemoveCookieFromList(matchIter);
  3001       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3002         "stale cookie was purged");
  3003       purgedList = CreatePurgeList(oldCookie);
  3005       // We've done all we need to wrt removing and notifying the stale cookie.
  3006       // From here on out, we pretend pretend it didn't exist, so that we
  3007       // preserve expected notification semantics when adding the new cookie.
  3008       foundCookie = false;
  3010     } else {
  3011       // If the old cookie is httponly, make sure we're not coming from script.
  3012       if (!aFromHttp && oldCookie->IsHttpOnly()) {
  3013         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3014           "previously stored cookie is httponly; coming from script");
  3015         return;
  3018       // Remove the old cookie.
  3019       RemoveCookieFromList(matchIter);
  3021       // If the new cookie has expired -- i.e. the intent was simply to delete
  3022       // the old cookie -- then we're done.
  3023       if (aCookie->Expiry() <= currentTime) {
  3024         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3025           "previously stored cookie was deleted");
  3026         NotifyChanged(oldCookie, MOZ_UTF16("deleted"));
  3027         return;
  3030       // Preserve creation time of cookie for ordering purposes.
  3031       aCookie->SetCreationTime(oldCookie->CreationTime());
  3034   } else {
  3035     // check if cookie has already expired
  3036     if (aCookie->Expiry() <= currentTime) {
  3037       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
  3038         "cookie has already expired");
  3039       return;
  3042     // check if we have to delete an old cookie.
  3043     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  3044     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
  3045       nsListIter iter;
  3046       FindStaleCookie(entry, currentTime, iter);
  3047       oldCookie = iter.Cookie();
  3049       // remove the oldest cookie from the domain
  3050       RemoveCookieFromList(iter);
  3051       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
  3052       purgedList = CreatePurgeList(oldCookie);
  3054     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
  3055       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
  3056       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
  3057       if (maxAge >= purgeAge) {
  3058         // we're over both size and age limits by 10%; time to purge the table!
  3059         // do this by:
  3060         // 1) removing expired cookies;
  3061         // 2) evicting the balance of old cookies until we reach the size limit.
  3062         // note that the cookieOldestTime indicator can be pessimistic - if it's
  3063         // older than the actual oldest cookie, we'll just purge more eagerly.
  3064         purgedList = PurgeCookies(aCurrentTimeInUsec);
  3069   // Add the cookie to the db. We do not supply a params array for batching
  3070   // because this might result in removals and additions being out of order.
  3071   AddCookieToList(aKey, aCookie, mDBState, nullptr);
  3072   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
  3074   // Now that list mutations are complete, notify observers. We do it here
  3075   // because observers may themselves attempt to mutate the list.
  3076   if (purgedList) {
  3077     NotifyChanged(purgedList, MOZ_UTF16("batch-deleted"));
  3080   NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
  3081                                      : MOZ_UTF16("added"));
  3084 /******************************************************************************
  3085  * nsCookieService impl:
  3086  * private cookie header parsing functions
  3087  ******************************************************************************/
  3089 // The following comment block elucidates the function of ParseAttributes.
  3090 /******************************************************************************
  3091  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
  3092  ** please note: this BNF deviates from both specifications, and reflects this
  3093  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
  3095  ** Differences from RFC2109/2616 and explanations:
  3096     1. implied *LWS
  3097          The grammar described by this specification is word-based. Except
  3098          where noted otherwise, linear white space (<LWS>) can be included
  3099          between any two adjacent words (token or quoted-string), and
  3100          between adjacent words and separators, without changing the
  3101          interpretation of a field.
  3102        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
  3104     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
  3105        common use inside values.
  3107     3. tokens and values have looser restrictions on allowed characters than
  3108        spec. This is also due to certain characters being in common use inside
  3109        values. We allow only '=' to separate token/value pairs, and ';' to
  3110        terminate tokens or values. <LWS> is allowed within tokens and values
  3111        (see bug 206022).
  3113     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
  3114        reject control chars or non-ASCII chars. This is erring on the loose
  3115        side, since there's probably no good reason to enforce this strictness.
  3117     5. cookie <NAME> is optional, where spec requires it. This is a fairly
  3118        trivial case, but allows the flexibility of setting only a cookie <VALUE>
  3119        with a blank <NAME> and is required by some sites (see bug 169091).
  3121     6. Attribute "HttpOnly", not covered in the RFCs, is supported
  3122        (see bug 178993).
  3124  ** Begin BNF:
  3125     token         = 1*<any allowed-chars except separators>
  3126     value         = 1*<any allowed-chars except value-sep>
  3127     separators    = ";" | "="
  3128     value-sep     = ";"
  3129     cookie-sep    = CR | LF
  3130     allowed-chars = <any OCTET except NUL or cookie-sep>
  3131     OCTET         = <any 8-bit sequence of data>
  3132     LWS           = SP | HT
  3133     NUL           = <US-ASCII NUL, null control character (0)>
  3134     CR            = <US-ASCII CR, carriage return (13)>
  3135     LF            = <US-ASCII LF, linefeed (10)>
  3136     SP            = <US-ASCII SP, space (32)>
  3137     HT            = <US-ASCII HT, horizontal-tab (9)>
  3139     set-cookie    = "Set-Cookie:" cookies
  3140     cookies       = cookie *( cookie-sep cookie )
  3141     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
  3142     NAME          = token                                ; cookie name
  3143     VALUE         = value                                ; cookie value
  3144     cookie-av     = token ["=" value]
  3146     valid values for cookie-av (checked post-parsing) are:
  3147     cookie-av     = "Path"    "=" value
  3148                   | "Domain"  "=" value
  3149                   | "Expires" "=" value
  3150                   | "Max-Age" "=" value
  3151                   | "Comment" "=" value
  3152                   | "Version" "=" value
  3153                   | "Secure"
  3154                   | "HttpOnly"
  3156 ******************************************************************************/
  3158 // helper functions for GetTokenValue
  3159 static inline bool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
  3160 static inline bool isterminator     (char c) { return c == '\n' || c == '\r'; }
  3161 static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
  3162 static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
  3164 // Parse a single token/value pair.
  3165 // Returns true if a cookie terminator is found, so caller can parse new cookie.
  3166 bool
  3167 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
  3168                                nsASingleFragmentCString::const_char_iterator &aEndIter,
  3169                                nsDependentCSubstring                         &aTokenString,
  3170                                nsDependentCSubstring                         &aTokenValue,
  3171                                bool                                          &aEqualsFound)
  3173   nsASingleFragmentCString::const_char_iterator start, lastSpace;
  3174   // initialize value string to clear garbage
  3175   aTokenValue.Rebind(aIter, aIter);
  3177   // find <token>, including any <LWS> between the end-of-token and the
  3178   // token separator. we'll remove trailing <LWS> next
  3179   while (aIter != aEndIter && iswhitespace(*aIter))
  3180     ++aIter;
  3181   start = aIter;
  3182   while (aIter != aEndIter && !istokenseparator(*aIter))
  3183     ++aIter;
  3185   // remove trailing <LWS>; first check we're not at the beginning
  3186   lastSpace = aIter;
  3187   if (lastSpace != start) {
  3188     while (--lastSpace != start && iswhitespace(*lastSpace))
  3189       continue;
  3190     ++lastSpace;
  3192   aTokenString.Rebind(start, lastSpace);
  3194   aEqualsFound = (*aIter == '=');
  3195   if (aEqualsFound) {
  3196     // find <value>
  3197     while (++aIter != aEndIter && iswhitespace(*aIter))
  3198       continue;
  3200     start = aIter;
  3202     // process <token>
  3203     // just look for ';' to terminate ('=' allowed)
  3204     while (aIter != aEndIter && !isvalueseparator(*aIter))
  3205       ++aIter;
  3207     // remove trailing <LWS>; first check we're not at the beginning
  3208     if (aIter != start) {
  3209       lastSpace = aIter;
  3210       while (--lastSpace != start && iswhitespace(*lastSpace))
  3211         continue;
  3212       aTokenValue.Rebind(start, ++lastSpace);
  3216   // aIter is on ';', or terminator, or EOS
  3217   if (aIter != aEndIter) {
  3218     // if on terminator, increment past & return true to process new cookie
  3219     if (isterminator(*aIter)) {
  3220       ++aIter;
  3221       return true;
  3223     // fall-through: aIter is on ';', increment and return false
  3224     ++aIter;
  3226   return false;
  3229 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
  3230 // cookie struct here, because we don't know which one to use until we've parsed the header.
  3231 bool
  3232 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
  3233                                  nsCookieAttributes &aCookieAttributes)
  3235   static const char kPath[]    = "path";
  3236   static const char kDomain[]  = "domain";
  3237   static const char kExpires[] = "expires";
  3238   static const char kMaxage[]  = "max-age";
  3239   static const char kSecure[]  = "secure";
  3240   static const char kHttpOnly[]  = "httponly";
  3242   nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
  3243   nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
  3244   aCookieHeader.BeginReading(cookieStart);
  3245   aCookieHeader.EndReading(cookieEnd);
  3247   aCookieAttributes.isSecure = false;
  3248   aCookieAttributes.isHttpOnly = false;
  3250   nsDependentCSubstring tokenString(cookieStart, cookieStart);
  3251   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
  3252   bool newCookie, equalsFound;
  3254   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
  3255   // if we find multiple cookies, return for processing
  3256   // note: if there's no '=', we assume token is <VALUE>. this is required by
  3257   //       some sites (see bug 169091).
  3258   // XXX fix the parser to parse according to <VALUE> grammar for this case
  3259   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3260   if (equalsFound) {
  3261     aCookieAttributes.name = tokenString;
  3262     aCookieAttributes.value = tokenValue;
  3263   } else {
  3264     aCookieAttributes.value = tokenString;
  3267   // extract remaining attributes
  3268   while (cookieStart != cookieEnd && !newCookie) {
  3269     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
  3271     if (!tokenValue.IsEmpty()) {
  3272       tokenValue.BeginReading(tempBegin);
  3273       tokenValue.EndReading(tempEnd);
  3276     // decide which attribute we have, and copy the string
  3277     if (tokenString.LowerCaseEqualsLiteral(kPath))
  3278       aCookieAttributes.path = tokenValue;
  3280     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
  3281       aCookieAttributes.host = tokenValue;
  3283     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
  3284       aCookieAttributes.expires = tokenValue;
  3286     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
  3287       aCookieAttributes.maxage = tokenValue;
  3289     // ignore any tokenValue for isSecure; just set the boolean
  3290     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
  3291       aCookieAttributes.isSecure = true;
  3293     // ignore any tokenValue for isHttpOnly (see bug 178993);
  3294     // just set the boolean
  3295     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
  3296       aCookieAttributes.isHttpOnly = true;
  3299   // rebind aCookieHeader, in case we need to process another cookie
  3300   aCookieHeader.Rebind(cookieStart, cookieEnd);
  3301   return newCookie;
  3304 /******************************************************************************
  3305  * nsCookieService impl:
  3306  * private domain & permission compliance enforcement functions
  3307  ******************************************************************************/
  3309 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
  3310 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
  3311 // dot may be present. If aHostURI is an IP address, an alias such as
  3312 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
  3313 // be the exact host, and aRequireHostMatch will be true to indicate that
  3314 // substring matches should not be performed.
  3315 nsresult
  3316 nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
  3317                                nsCString &aBaseDomain,
  3318                                bool      &aRequireHostMatch)
  3320   // get the base domain. this will fail if the host contains a leading dot,
  3321   // more than one trailing dot, or is otherwise malformed.
  3322   nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
  3323   aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3324                       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
  3325   if (aRequireHostMatch) {
  3326     // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
  3327     // such as 'co.uk', or the empty string. use the host as a key in such
  3328     // cases.
  3329     rv = aHostURI->GetAsciiHost(aBaseDomain);
  3331   NS_ENSURE_SUCCESS(rv, rv);
  3333   // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
  3334   if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
  3335     return NS_ERROR_INVALID_ARG;
  3337   // block any URIs without a host that aren't file:// URIs.
  3338   if (aBaseDomain.IsEmpty()) {
  3339     bool isFileURI = false;
  3340     aHostURI->SchemeIs("file", &isFileURI);
  3341     if (!isFileURI)
  3342       return NS_ERROR_INVALID_ARG;
  3345   return NS_OK;
  3348 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
  3349 // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
  3350 // that aHost is already normalized, and it may contain a leading dot
  3351 // (indicating that it represents a domain). A trailing dot may be present.
  3352 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
  3353 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
  3354 // leading dot will be treated as an error.
  3355 nsresult
  3356 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
  3357                                        nsCString        &aBaseDomain)
  3359   // aHost must not be the string '.'.
  3360   if (aHost.Length() == 1 && aHost.Last() == '.')
  3361     return NS_ERROR_INVALID_ARG;
  3363   // aHost may contain a leading dot; if so, strip it now.
  3364   bool domain = !aHost.IsEmpty() && aHost.First() == '.';
  3366   // get the base domain. this will fail if the host contains a leading dot,
  3367   // more than one trailing dot, or is otherwise malformed.
  3368   nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
  3369   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
  3370       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
  3371     // aHost is either an IP address, an alias such as 'localhost', an eTLD
  3372     // such as 'co.uk', or the empty string. use the host as a key in such
  3373     // cases; however, we reject any such hosts with a leading dot, since it
  3374     // doesn't make sense for them to be domain cookies.
  3375     if (domain)
  3376       return NS_ERROR_INVALID_ARG;
  3378     aBaseDomain = aHost;
  3379     return NS_OK;
  3381   return rv;
  3384 // Normalizes the given hostname, component by component. ASCII/ACE
  3385 // components are lower-cased, and UTF-8 components are normalized per
  3386 // RFC 3454 and converted to ACE.
  3387 nsresult
  3388 nsCookieService::NormalizeHost(nsCString &aHost)
  3390   if (!IsASCII(aHost)) {
  3391     nsAutoCString host;
  3392     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
  3393     if (NS_FAILED(rv))
  3394       return rv;
  3396     aHost = host;
  3399   ToLowerCase(aHost);
  3400   return NS_OK;
  3403 // returns true if 'a' is equal to or a subdomain of 'b',
  3404 // assuming no leading dots are present.
  3405 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
  3407   if (a == b)
  3408     return true;
  3409   if (a.Length() > b.Length())
  3410     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
  3411   return false;
  3414 CookieStatus
  3415 nsCookieService::CheckPrefs(nsIURI          *aHostURI,
  3416                             bool             aIsForeign,
  3417                             bool             aRequireHostMatch,
  3418                             const char      *aCookieHeader)
  3420   nsresult rv;
  3422   // don't let ftp sites get/set cookies (could be a security issue)
  3423   bool ftp;
  3424   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
  3425     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
  3426     return STATUS_REJECTED_WITH_ERROR;
  3429   // check the permission list first; if we find an entry, it overrides
  3430   // default prefs. see bug 184059.
  3431   if (mPermissionService) {
  3432     nsCookieAccess access;
  3433     // Not passing an nsIChannel here is probably OK; our implementation
  3434     // doesn't do anything with it anyway.
  3435     rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
  3437     // if we found an entry, use it
  3438     if (NS_SUCCEEDED(rv)) {
  3439       switch (access) {
  3440       case nsICookiePermission::ACCESS_DENY:
  3441         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3442                           aCookieHeader, "cookies are blocked for this site");
  3443         return STATUS_REJECTED;
  3445       case nsICookiePermission::ACCESS_ALLOW:
  3446         return STATUS_ACCEPTED;
  3448       case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
  3449         if (aIsForeign) {
  3450           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3451                             aCookieHeader, "third party cookies are blocked "
  3452                             "for this site");
  3453           return STATUS_REJECTED;
  3456         return STATUS_ACCEPTED;
  3458       case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
  3459         if (!aIsForeign)
  3460           return STATUS_ACCEPTED;
  3461         uint32_t priorCookieCount = 0;
  3462         nsAutoCString hostFromURI;
  3463         aHostURI->GetHost(hostFromURI);
  3464         CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3465         if (priorCookieCount == 0) {
  3466           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
  3467                             aCookieHeader, "third party cookies are blocked "
  3468                             "for this site");
  3469           return STATUS_REJECTED;
  3471         return STATUS_ACCEPTED;
  3476   // check default prefs
  3477   if (mCookieBehavior == BEHAVIOR_REJECT) {
  3478     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
  3479     return STATUS_REJECTED;
  3482   // check if cookie is foreign
  3483   if (aIsForeign) {
  3484     if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
  3485       return STATUS_ACCEPT_SESSION;
  3487     if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
  3488       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3489       return STATUS_REJECTED;
  3492     if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) {
  3493       uint32_t priorCookieCount = 0;
  3494       nsAutoCString hostFromURI;
  3495       aHostURI->GetHost(hostFromURI);
  3496       CountCookiesFromHost(hostFromURI, &priorCookieCount);
  3497       if (priorCookieCount == 0) {
  3498         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
  3499         return STATUS_REJECTED;
  3501       if (mThirdPartySession)
  3502         return STATUS_ACCEPT_SESSION;
  3506   // if nothing has complained, accept cookie
  3507   return STATUS_ACCEPTED;
  3510 // processes domain attribute, and returns true if host has permission to set for this domain.
  3511 bool
  3512 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
  3513                              nsIURI             *aHostURI,
  3514                              const nsCString    &aBaseDomain,
  3515                              bool                aRequireHostMatch)
  3517   // get host from aHostURI
  3518   nsAutoCString hostFromURI;
  3519   aHostURI->GetAsciiHost(hostFromURI);
  3521   // if a domain is given, check the host has permission
  3522   if (!aCookieAttributes.host.IsEmpty()) {
  3523     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
  3524     if (aCookieAttributes.host.Length() > 1 &&
  3525         aCookieAttributes.host.First() == '.') {
  3526       aCookieAttributes.host.Cut(0, 1);
  3529     // switch to lowercase now, to avoid case-insensitive compares everywhere
  3530     ToLowerCase(aCookieAttributes.host);
  3532     // check whether the host is either an IP address, an alias such as
  3533     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
  3534     // cases, require an exact string match for the domain, and leave the cookie
  3535     // as a non-domain one. bug 105917 originally noted the requirement to deal
  3536     // with IP addresses.
  3537     if (aRequireHostMatch)
  3538       return hostFromURI.Equals(aCookieAttributes.host);
  3540     // ensure the proposed domain is derived from the base domain; and also
  3541     // that the host domain is derived from the proposed domain (per RFC2109).
  3542     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
  3543         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
  3544       // prepend a dot to indicate a domain cookie
  3545       aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
  3546       return true;
  3549     /*
  3550      * note: RFC2109 section 4.3.2 requires that we check the following:
  3551      * that the portion of host not in domain does not contain a dot.
  3552      * this prevents hosts of the form x.y.co.nz from setting cookies in the
  3553      * entire .co.nz domain. however, it's only a only a partial solution and
  3554      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
  3555      */
  3556     return false;
  3559   // no domain specified, use hostFromURI
  3560   aCookieAttributes.host = hostFromURI;
  3561   return true;
  3564 bool
  3565 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
  3566                            nsIURI             *aHostURI)
  3568   // if a path is given, check the host has permission
  3569   if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
  3570     // strip down everything after the last slash to get the path,
  3571     // ignoring slashes in the query string part.
  3572     // if we can QI to nsIURL, that'll take care of the query string portion.
  3573     // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
  3574     nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
  3575     if (hostURL) {
  3576       hostURL->GetDirectory(aCookieAttributes.path);
  3577     } else {
  3578       aHostURI->GetPath(aCookieAttributes.path);
  3579       int32_t slash = aCookieAttributes.path.RFindChar('/');
  3580       if (slash != kNotFound) {
  3581         aCookieAttributes.path.Truncate(slash + 1);
  3585 #if 0
  3586   } else {
  3587     /**
  3588      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
  3589      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
  3590      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
  3591      * been disabled, unless we can evangelize these sites.
  3592      */
  3593     // get path from aHostURI
  3594     nsAutoCString pathFromURI;
  3595     if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
  3596         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
  3597       return false;
  3599 #endif
  3602   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
  3603       aCookieAttributes.path.FindChar('\t') != kNotFound )
  3604     return false;
  3606   return true;
  3609 bool
  3610 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
  3611                            int64_t             aServerTime,
  3612                            int64_t             aCurrentTime)
  3614   /* Determine when the cookie should expire. This is done by taking the difference between 
  3615    * the server time and the time the server wants the cookie to expire, and adding that 
  3616    * difference to the client time. This localizes the client time regardless of whether or
  3617    * not the TZ environment variable was set on the client.
  3619    * Note: We need to consider accounting for network lag here, per RFC.
  3620    */
  3621   int64_t delta;
  3623   // check for max-age attribute first; this overrides expires attribute
  3624   if (!aCookieAttributes.maxage.IsEmpty()) {
  3625     // obtain numeric value of maxageAttribute
  3626     int64_t maxage;
  3627     int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
  3629     // default to session cookie if the conversion failed
  3630     if (numInts != 1) {
  3631       return true;
  3634     delta = maxage;
  3636   // check for expires attribute
  3637   } else if (!aCookieAttributes.expires.IsEmpty()) {
  3638     PRTime expires;
  3640     // parse expiry time
  3641     if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
  3642       return true;
  3645     delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
  3647   // default to session cookie if no attributes found
  3648   } else {
  3649     return true;
  3652   // if this addition overflows, expiryTime will be less than currentTime
  3653   // and the cookie will be expired - that's okay.
  3654   aCookieAttributes.expiryTime = aCurrentTime + delta;
  3656   return false;
  3659 /******************************************************************************
  3660  * nsCookieService impl:
  3661  * private cookielist management functions
  3662  ******************************************************************************/
  3664 void
  3665 nsCookieService::RemoveAllFromMemory()
  3667   // clearing the hashtable will call each nsCookieEntry's dtor,
  3668   // which releases all their respective children.
  3669   mDBState->hostTable.Clear();
  3670   mDBState->cookieCount = 0;
  3671   mDBState->cookieOldestTime = INT64_MAX;
  3674 // stores temporary data for enumerating over the hash entries,
  3675 // since enumeration is done using callback functions
  3676 struct nsPurgeData
  3678   typedef nsTArray<nsListIter> ArrayType;
  3680   nsPurgeData(int64_t aCurrentTime,
  3681               int64_t aPurgeTime,
  3682               ArrayType &aPurgeList,
  3683               nsIMutableArray *aRemovedList,
  3684               mozIStorageBindingParamsArray *aParamsArray)
  3685    : currentTime(aCurrentTime)
  3686    , purgeTime(aPurgeTime)
  3687    , oldestTime(INT64_MAX)
  3688    , purgeList(aPurgeList)
  3689    , removedList(aRemovedList)
  3690    , paramsArray(aParamsArray)
  3694   // the current time, in seconds
  3695   int64_t currentTime;
  3697   // lastAccessed time older than which cookies are eligible for purge
  3698   int64_t purgeTime;
  3700   // lastAccessed time of the oldest cookie found during purge, to update our indicator
  3701   int64_t oldestTime;
  3703   // list of cookies over the age limit, for purging
  3704   ArrayType &purgeList;
  3706   // list of all cookies we've removed, for notification
  3707   nsIMutableArray *removedList;
  3709   // The array of parameters to be bound to the statement for deletion later.
  3710   mozIStorageBindingParamsArray *paramsArray;
  3711 };
  3713 // comparator class for lastaccessed times of cookies.
  3714 class CompareCookiesByAge {
  3715 public:
  3716   bool Equals(const nsListIter &a, const nsListIter &b) const
  3718     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
  3719            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
  3722   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3724     // compare by lastAccessed time, and tiebreak by creationTime.
  3725     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
  3726     if (result != 0)
  3727       return result < 0;
  3729     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
  3731 };
  3733 // comparator class for sorting cookies by entry and index.
  3734 class CompareCookiesByIndex {
  3735 public:
  3736   bool Equals(const nsListIter &a, const nsListIter &b) const
  3738     NS_ASSERTION(a.entry != b.entry || a.index != b.index,
  3739       "cookie indexes should never be equal");
  3740     return false;
  3743   bool LessThan(const nsListIter &a, const nsListIter &b) const
  3745     // compare by entryclass pointer, then by index.
  3746     if (a.entry != b.entry)
  3747       return a.entry < b.entry;
  3749     return a.index < b.index;
  3751 };
  3753 PLDHashOperator
  3754 purgeCookiesCallback(nsCookieEntry *aEntry,
  3755                      void          *aArg)
  3757   nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
  3759   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3760   mozIStorageBindingParamsArray *array = data.paramsArray;
  3761   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
  3762     nsListIter iter(aEntry, i);
  3763     nsCookie *cookie = cookies[i];
  3765     // check if the cookie has expired
  3766     if (cookie->Expiry() <= data.currentTime) {
  3767       data.removedList->AppendElement(cookie, false);
  3768       COOKIE_LOGEVICTED(cookie, "Cookie expired");
  3770       // remove from list; do not increment our iterator
  3771       gCookieService->RemoveCookieFromList(iter, array);
  3773     } else {
  3774       // check if the cookie is over the age limit
  3775       if (cookie->LastAccessed() <= data.purgeTime) {
  3776         data.purgeList.AppendElement(iter);
  3778       } else if (cookie->LastAccessed() < data.oldestTime) {
  3779         // reset our indicator
  3780         data.oldestTime = cookie->LastAccessed();
  3783       ++i;
  3786   return PL_DHASH_NEXT;
  3789 // purges expired and old cookies in a batch operation.
  3790 already_AddRefed<nsIArray>
  3791 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
  3793   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
  3794   EnsureReadComplete();
  3796 #ifdef PR_LOGGING
  3797   uint32_t initialCookieCount = mDBState->cookieCount;
  3798   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3799     ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
  3800      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3801 #endif
  3803   nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
  3805   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
  3807   // Create a params array to batch the removals. This is OK here because
  3808   // all the removals are in order, and there are no interleaved additions.
  3809   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  3810   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
  3811   if (mDBState->dbConn) {
  3812     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  3815   nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
  3816     aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
  3817   mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
  3819 #ifdef PR_LOGGING
  3820   uint32_t postExpiryCookieCount = mDBState->cookieCount;
  3821 #endif
  3823   // now we have a list of iterators for cookies over the age limit.
  3824   // sort them by age, and then we'll see how many to remove...
  3825   purgeList.Sort(CompareCookiesByAge());
  3827   // only remove old cookies until we reach the max cookie limit, no more.
  3828   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
  3829     mDBState->cookieCount - mMaxNumberOfCookies : 0;
  3830   if (purgeList.Length() > excess) {
  3831     // We're not purging everything in the list, so update our indicator.
  3832     data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
  3834     purgeList.SetLength(excess);
  3837   // sort the list again, this time grouping cookies with a common entryclass
  3838   // together, and with ascending index. this allows us to iterate backwards
  3839   // over the list removing cookies, without having to adjust indexes as we go.
  3840   purgeList.Sort(CompareCookiesByIndex());
  3841   for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
  3842     nsCookie *cookie = purgeList[i].Cookie();
  3843     removedList->AppendElement(cookie, false);
  3844     COOKIE_LOGEVICTED(cookie, "Cookie too old");
  3846     RemoveCookieFromList(purgeList[i], paramsArray);
  3849   // Update the database if we have entries to purge.
  3850   if (paramsArray) {
  3851     uint32_t length;
  3852     paramsArray->GetLength(&length);
  3853     if (length) {
  3854       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  3855       NS_ASSERT_SUCCESS(rv);
  3856       nsCOMPtr<mozIStoragePendingStatement> handle;
  3857       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  3858       NS_ASSERT_SUCCESS(rv);
  3862   // reset the oldest time indicator
  3863   mDBState->cookieOldestTime = data.oldestTime;
  3865   COOKIE_LOGSTRING(PR_LOG_DEBUG,
  3866     ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
  3867      initialCookieCount - postExpiryCookieCount,
  3868      postExpiryCookieCount - mDBState->cookieCount,
  3869      mDBState->cookieCount,
  3870      aCurrentTimeInUsec - mDBState->cookieOldestTime));
  3872   return removedList.forget();
  3875 // find whether a given cookie has been previously set. this is provided by the
  3876 // nsICookieManager2 interface.
  3877 NS_IMETHODIMP
  3878 nsCookieService::CookieExists(nsICookie2 *aCookie,
  3879                               bool       *aFoundCookie)
  3881   NS_ENSURE_ARG_POINTER(aCookie);
  3883   if (!mDBState) {
  3884     NS_WARNING("No DBState! Profile already closed?");
  3885     return NS_ERROR_NOT_AVAILABLE;
  3888   nsAutoCString host, name, path;
  3889   nsresult rv = aCookie->GetHost(host);
  3890   NS_ENSURE_SUCCESS(rv, rv);
  3891   rv = aCookie->GetName(name);
  3892   NS_ENSURE_SUCCESS(rv, rv);
  3893   rv = aCookie->GetPath(path);
  3894   NS_ENSURE_SUCCESS(rv, rv);
  3896   nsAutoCString baseDomain;
  3897   rv = GetBaseDomainFromHost(host, baseDomain);
  3898   NS_ENSURE_SUCCESS(rv, rv);
  3900   nsListIter iter;
  3901   *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), host, name, path, iter);
  3902   return NS_OK;
  3905 // For a given base domain, find either an expired cookie or the oldest cookie
  3906 // by lastAccessed time.
  3907 void
  3908 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
  3909                                  int64_t aCurrentTime,
  3910                                  nsListIter &aIter)
  3912   aIter.entry = nullptr;
  3914   int64_t oldestTime = 0;
  3915   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
  3916   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  3917     nsCookie *cookie = cookies[i];
  3919     // If we found an expired cookie, we're done.
  3920     if (cookie->Expiry() <= aCurrentTime) {
  3921       aIter.entry = aEntry;
  3922       aIter.index = i;
  3923       return;
  3926     // Check if we've found the oldest cookie so far.
  3927     if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
  3928       oldestTime = cookie->LastAccessed();
  3929       aIter.entry = aEntry;
  3930       aIter.index = i;
  3935 // count the number of cookies stored by a particular host. this is provided by the
  3936 // nsICookieManager2 interface.
  3937 NS_IMETHODIMP
  3938 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
  3939                                       uint32_t         *aCountFromHost)
  3941   if (!mDBState) {
  3942     NS_WARNING("No DBState! Profile already closed?");
  3943     return NS_ERROR_NOT_AVAILABLE;
  3946   // first, normalize the hostname, and fail if it contains illegal characters.
  3947   nsAutoCString host(aHost);
  3948   nsresult rv = NormalizeHost(host);
  3949   NS_ENSURE_SUCCESS(rv, rv);
  3951   nsAutoCString baseDomain;
  3952   rv = GetBaseDomainFromHost(host, baseDomain);
  3953   NS_ENSURE_SUCCESS(rv, rv);
  3955   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  3956   EnsureReadDomain(key);
  3958   // Return a count of all cookies, including expired.
  3959   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  3960   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
  3961   return NS_OK;
  3964 // get an enumerator of cookies stored by a particular host. this is provided by the
  3965 // nsICookieManager2 interface.
  3966 NS_IMETHODIMP
  3967 nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
  3968                                     nsISimpleEnumerator **aEnumerator)
  3970   if (!mDBState) {
  3971     NS_WARNING("No DBState! Profile already closed?");
  3972     return NS_ERROR_NOT_AVAILABLE;
  3975   // first, normalize the hostname, and fail if it contains illegal characters.
  3976   nsAutoCString host(aHost);
  3977   nsresult rv = NormalizeHost(host);
  3978   NS_ENSURE_SUCCESS(rv, rv);
  3980   nsAutoCString baseDomain;
  3981   rv = GetBaseDomainFromHost(host, baseDomain);
  3982   NS_ENSURE_SUCCESS(rv, rv);
  3984   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
  3985   EnsureReadDomain(key);
  3987   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
  3988   if (!entry)
  3989     return NS_NewEmptyEnumerator(aEnumerator);
  3991   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
  3992   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  3993   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  3994     cookieList.AppendObject(cookies[i]);
  3997   return NS_NewArrayEnumerator(aEnumerator, cookieList);
  4000 namespace {
  4002 /**
  4003  * This structure is used as a in/out parameter when enumerating the cookies
  4004  * for an app.
  4005  * It will contain the app id and onlyBrowserElement flag information as input
  4006  * and will contain the array of matching cookies as output.
  4007  */
  4008 struct GetCookiesForAppStruct {
  4009   uint32_t              appId;
  4010   bool                  onlyBrowserElement;
  4011   nsCOMArray<nsICookie> cookies;
  4013   GetCookiesForAppStruct() MOZ_DELETE;
  4014   GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
  4015     : appId(aAppId)
  4016     , onlyBrowserElement(aOnlyBrowserElement)
  4017   {}
  4018 };
  4020 } // anonymous namespace
  4022 /* static */ PLDHashOperator
  4023 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
  4025   GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
  4027   if (entry->mAppId != data->appId ||
  4028       (data->onlyBrowserElement && !entry->mInBrowserElement)) {
  4029     return PL_DHASH_NEXT;
  4032   const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
  4034   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4035     data->cookies.AppendObject(cookies[i]);
  4038   return PL_DHASH_NEXT;
  4041 NS_IMETHODIMP
  4042 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
  4043                                   nsISimpleEnumerator** aEnumerator)
  4045   if (!mDBState) {
  4046     NS_WARNING("No DBState! Profile already closed?");
  4047     return NS_ERROR_NOT_AVAILABLE;
  4050   NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
  4052   GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
  4053   mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
  4055   return NS_NewArrayEnumerator(aEnumerator, data.cookies);
  4058 NS_IMETHODIMP
  4059 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
  4061   nsCOMPtr<nsISimpleEnumerator> enumerator;
  4062   nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
  4063                                  getter_AddRefs(enumerator));
  4065   NS_ENSURE_SUCCESS(rv, rv);
  4067   bool hasMore;
  4068   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
  4069     nsCOMPtr<nsISupports> supports;
  4070     nsCOMPtr<nsICookie> cookie;
  4071     rv = enumerator->GetNext(getter_AddRefs(supports));
  4072     NS_ENSURE_SUCCESS(rv, rv);
  4074     cookie = do_QueryInterface(supports);
  4076     nsAutoCString host;
  4077     cookie->GetHost(host);
  4079     nsAutoCString name;
  4080     cookie->GetName(name);
  4082     nsAutoCString path;
  4083     cookie->GetPath(path);
  4085     // nsICookie do not carry the appId/inBrowserElement information.
  4086     // That means we have to guess. This is easy for appId but not for
  4087     // inBrowserElement flag.
  4088     // A simple solution is to always ask to remove the cookie with
  4089     // inBrowserElement = true and only ask for the other one to be removed if
  4090     // we happen to be in the case of !aOnlyBrowserElement.
  4091     // Anyway, with this solution, we will likely be looking for unexistant
  4092     // cookies.
  4093     //
  4094     // NOTE: we could make this better by getting nsCookieEntry objects instead
  4095     // of plain nsICookie.
  4096     Remove(host, aAppId, true, name, path, false);
  4097     if (!aOnlyBrowserElement) {
  4098       Remove(host, aAppId, false, name, path, false);
  4102   return NS_OK;
  4105 // find an exact cookie specified by host, name, and path that hasn't expired.
  4106 bool
  4107 nsCookieService::FindCookie(const nsCookieKey    &aKey,
  4108                             const nsAFlatCString &aHost,
  4109                             const nsAFlatCString &aName,
  4110                             const nsAFlatCString &aPath,
  4111                             nsListIter           &aIter)
  4113   EnsureReadDomain(aKey);
  4115   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
  4116   if (!entry)
  4117     return false;
  4119   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
  4120   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
  4121     nsCookie *cookie = cookies[i];
  4123     if (aHost.Equals(cookie->Host()) &&
  4124         aPath.Equals(cookie->Path()) &&
  4125         aName.Equals(cookie->Name())) {
  4126       aIter = nsListIter(entry, i);
  4127       return true;
  4131   return false;
  4134 // remove a cookie from the hashtable, and update the iterator state.
  4135 void
  4136 nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
  4137                                       mozIStorageBindingParamsArray *aParamsArray)
  4139   // if it's a non-session cookie, remove it from the db
  4140   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
  4141     // Use the asynchronous binding methods to ensure that we do not acquire
  4142     // the database lock.
  4143     mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
  4144     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4145     if (!paramsArray) {
  4146       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4149     nsCOMPtr<mozIStorageBindingParams> params;
  4150     paramsArray->NewBindingParams(getter_AddRefs(params));
  4152     DebugOnly<nsresult> rv =
  4153       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4154                                    aIter.Cookie()->Name());
  4155     NS_ASSERT_SUCCESS(rv);
  4157     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4158                                       aIter.Cookie()->Host());
  4159     NS_ASSERT_SUCCESS(rv);
  4161     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4162                                       aIter.Cookie()->Path());
  4163     NS_ASSERT_SUCCESS(rv);
  4165     rv = paramsArray->AddParams(params);
  4166     NS_ASSERT_SUCCESS(rv);
  4168     // If we weren't given a params array, we'll need to remove it ourselves.
  4169     if (!aParamsArray) {
  4170       rv = stmt->BindParameters(paramsArray);
  4171       NS_ASSERT_SUCCESS(rv);
  4172       nsCOMPtr<mozIStoragePendingStatement> handle;
  4173       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
  4174       NS_ASSERT_SUCCESS(rv);
  4178   if (aIter.entry->GetCookies().Length() == 1) {
  4179     // we're removing the last element in the array - so just remove the entry
  4180     // from the hash. note that the entryclass' dtor will take care of
  4181     // releasing this last element for us!
  4182     mDBState->hostTable.RawRemoveEntry(aIter.entry);
  4184   } else {
  4185     // just remove the element from the list
  4186     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
  4189   --mDBState->cookieCount;
  4192 void
  4193 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
  4194                      const nsCookieKey &aKey,
  4195                      const nsCookie *aCookie)
  4197   NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
  4198   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
  4200   // Use the asynchronous binding methods to ensure that we do not acquire the
  4201   // database lock.
  4202   nsCOMPtr<mozIStorageBindingParams> params;
  4203   DebugOnly<nsresult> rv =
  4204     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4205   NS_ASSERT_SUCCESS(rv);
  4207   // Bind our values to params
  4208   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
  4209                                     aKey.mBaseDomain);
  4210   NS_ASSERT_SUCCESS(rv);
  4212   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
  4213                                aKey.mAppId);
  4214   NS_ASSERT_SUCCESS(rv);
  4216   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
  4217                                aKey.mInBrowserElement ? 1 : 0);
  4218   NS_ASSERT_SUCCESS(rv);
  4220   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4221                                     aCookie->Name());
  4222   NS_ASSERT_SUCCESS(rv);
  4224   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
  4225                                     aCookie->Value());
  4226   NS_ASSERT_SUCCESS(rv);
  4228   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4229                                     aCookie->Host());
  4230   NS_ASSERT_SUCCESS(rv);
  4232   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4233                                     aCookie->Path());
  4234   NS_ASSERT_SUCCESS(rv);
  4236   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
  4237                                aCookie->Expiry());
  4238   NS_ASSERT_SUCCESS(rv);
  4240   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4241                                aCookie->LastAccessed());
  4242   NS_ASSERT_SUCCESS(rv);
  4244   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
  4245                                aCookie->CreationTime());
  4246   NS_ASSERT_SUCCESS(rv);
  4248   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
  4249                                aCookie->IsSecure());
  4250   NS_ASSERT_SUCCESS(rv);
  4252   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
  4253                                aCookie->IsHttpOnly());
  4254   NS_ASSERT_SUCCESS(rv);
  4256   // Bind the params to the array.
  4257   rv = aParamsArray->AddParams(params);
  4258   NS_ASSERT_SUCCESS(rv);
  4261 void
  4262 nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
  4263                                  nsCookie                      *aCookie,
  4264                                  DBState                       *aDBState,
  4265                                  mozIStorageBindingParamsArray *aParamsArray,
  4266                                  bool                           aWriteToDB)
  4268   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
  4269                "Not writing to the DB but have a params array?");
  4270   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
  4271                "Do not have a DB connection but have a params array?");
  4273   nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
  4274   NS_ASSERTION(entry, "can't insert element into a null entry!");
  4276   entry->GetCookies().AppendElement(aCookie);
  4277   ++aDBState->cookieCount;
  4279   // keep track of the oldest cookie, for when it comes time to purge
  4280   if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
  4281     aDBState->cookieOldestTime = aCookie->LastAccessed();
  4283   // if it's a non-session cookie and hasn't just been read from the db, write it out.
  4284   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
  4285     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
  4286     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
  4287     if (!paramsArray) {
  4288       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
  4290     bindCookieParameters(paramsArray, aKey, aCookie);
  4292     // If we were supplied an array to store parameters, we shouldn't call
  4293     // executeAsync - someone up the stack will do this for us.
  4294     if (!aParamsArray) {
  4295       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
  4296       NS_ASSERT_SUCCESS(rv);
  4297       nsCOMPtr<mozIStoragePendingStatement> handle;
  4298       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
  4299       NS_ASSERT_SUCCESS(rv);
  4304 void
  4305 nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
  4306                                     int64_t                        aLastAccessed,
  4307                                     mozIStorageBindingParamsArray *aParamsArray)
  4309   NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
  4311   // udpate the lastAccessed timestamp
  4312   aCookie->SetLastAccessed(aLastAccessed);
  4314   // if it's a non-session cookie, update it in the db too
  4315   if (!aCookie->IsSession() && aParamsArray) {
  4316     // Create our params holder.
  4317     nsCOMPtr<mozIStorageBindingParams> params;
  4318     aParamsArray->NewBindingParams(getter_AddRefs(params));
  4320     // Bind our parameters.
  4321     DebugOnly<nsresult> rv =
  4322       params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
  4323                               aLastAccessed);
  4324     NS_ASSERT_SUCCESS(rv);
  4326     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
  4327                                       aCookie->Name());
  4328     NS_ASSERT_SUCCESS(rv);
  4330     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
  4331                                       aCookie->Host());
  4332     NS_ASSERT_SUCCESS(rv);
  4334     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
  4335                                       aCookie->Path());
  4336     NS_ASSERT_SUCCESS(rv);
  4338     // Add our bound parameters to the array.
  4339     rv = aParamsArray->AddParams(params);
  4340     NS_ASSERT_SUCCESS(rv);
  4344 size_t
  4345 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  4347   size_t n = aMallocSizeOf(this);
  4349   if (mDefaultDBState) {
  4350     n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
  4352   if (mPrivateDBState) {
  4353     n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
  4356   return n;
  4359 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
  4361 NS_IMETHODIMP
  4362 nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
  4363                                 nsISupports* aData)
  4365   return MOZ_COLLECT_REPORT(
  4366     "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
  4367     SizeOfIncludingThis(CookieServiceMallocSizeOf),
  4368     "Memory used by the cookie service.");

mercurial