LCOV - code coverage report
Current view: top level - netwerk/cookie - nsCookieService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 276 2132 12.9 %
Date: 2017-07-14 16:53:18 Functions: 48 181 26.5 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       6             : 
       7             : #include "mozilla/Attributes.h"
       8             : #include "mozilla/DebugOnly.h"
       9             : #include "mozilla/Likely.h"
      10             : #include "mozilla/Printf.h"
      11             : #include "mozilla/SizePrintfMacros.h"
      12             : #include "mozilla/Unused.h"
      13             : 
      14             : #include "mozilla/net/CookieServiceChild.h"
      15             : #include "mozilla/net/NeckoCommon.h"
      16             : 
      17             : #include "nsCookieService.h"
      18             : #include "nsContentUtils.h"
      19             : #include "nsIServiceManager.h"
      20             : #include "nsIScriptSecurityManager.h"
      21             : 
      22             : #include "nsIIOService.h"
      23             : #include "nsIPrefBranch.h"
      24             : #include "nsIPrefService.h"
      25             : #include "nsIScriptError.h"
      26             : #include "nsICookiePermission.h"
      27             : #include "nsIURI.h"
      28             : #include "nsIURL.h"
      29             : #include "nsIChannel.h"
      30             : #include "nsIFile.h"
      31             : #include "nsIObserverService.h"
      32             : #include "nsILineInputStream.h"
      33             : #include "nsIEffectiveTLDService.h"
      34             : #include "nsIIDNService.h"
      35             : #include "mozIThirdPartyUtil.h"
      36             : 
      37             : #include "nsTArray.h"
      38             : #include "nsCOMArray.h"
      39             : #include "nsIMutableArray.h"
      40             : #include "nsArrayEnumerator.h"
      41             : #include "nsEnumeratorUtils.h"
      42             : #include "nsAutoPtr.h"
      43             : #include "nsReadableUtils.h"
      44             : #include "nsCRT.h"
      45             : #include "prprf.h"
      46             : #include "nsNetUtil.h"
      47             : #include "nsNetCID.h"
      48             : #include "nsISimpleEnumerator.h"
      49             : #include "nsIInputStream.h"
      50             : #include "nsAppDirectoryServiceDefs.h"
      51             : #include "nsNetCID.h"
      52             : #include "mozilla/storage.h"
      53             : #include "mozilla/AutoRestore.h"
      54             : #include "mozilla/FileUtils.h"
      55             : #include "mozilla/Telemetry.h"
      56             : #include "nsIConsoleService.h"
      57             : #include "nsVariant.h"
      58             : 
      59             : using namespace mozilla;
      60             : using namespace mozilla::net;
      61             : 
      62             : // Create key from baseDomain that will access the default cookie namespace.
      63             : // TODO: When we figure out what the API will look like for nsICookieManager{2}
      64             : // on content processes (see bug 777620), change to use the appropriate app
      65             : // namespace.  For now those IDLs aren't supported on child processes.
      66             : #define DEFAULT_APP_KEY(baseDomain) \
      67             :         nsCookieKey(baseDomain, OriginAttributes())
      68             : 
      69             : /******************************************************************************
      70             :  * nsCookieService impl:
      71             :  * useful types & constants
      72             :  ******************************************************************************/
      73             : 
      74             : static nsCookieService *gCookieService;
      75             : 
      76             : // XXX_hack. See bug 178993.
      77             : // This is a hack to hide HttpOnly cookies from older browsers
      78             : #define HTTP_ONLY_PREFIX "#HttpOnly_"
      79             : 
      80             : #define COOKIES_FILE "cookies.sqlite"
      81             : #define COOKIES_SCHEMA_VERSION 8
      82             : 
      83             : // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
      84             : // ReadCookieDBListener::HandleResult
      85             : #define IDX_NAME 0
      86             : #define IDX_VALUE 1
      87             : #define IDX_HOST 2
      88             : #define IDX_PATH 3
      89             : #define IDX_EXPIRY 4
      90             : #define IDX_LAST_ACCESSED 5
      91             : #define IDX_CREATION_TIME 6
      92             : #define IDX_SECURE 7
      93             : #define IDX_HTTPONLY 8
      94             : #define IDX_BASE_DOMAIN 9
      95             : #define IDX_ORIGIN_ATTRIBUTES 10
      96             : 
      97             : #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
      98             : 
      99             : static const int64_t kCookiePurgeAge =
     100             :   int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
     101             : 
     102             : #define OLD_COOKIE_FILE_NAME "cookies.txt"
     103             : 
     104             : #undef  LIMIT
     105             : #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
     106             : 
     107             : #undef  ADD_TEN_PERCENT
     108             : #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
     109             : 
     110             : // default limits for the cookie list. these can be tuned by the
     111             : // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
     112             : static const uint32_t kMaxNumberOfCookies = 3000;
     113             : static const uint32_t kMaxCookiesPerHost  = 150;
     114             : static const uint32_t kMaxBytesPerCookie  = 4096;
     115             : static const uint32_t kMaxBytesPerPath    = 1024;
     116             : 
     117             : // pref string constants
     118             : static const char kPrefCookieBehavior[]       = "network.cookie.cookieBehavior";
     119             : static const char kPrefMaxNumberOfCookies[]   = "network.cookie.maxNumber";
     120             : static const char kPrefMaxCookiesPerHost[]    = "network.cookie.maxPerHost";
     121             : static const char kPrefCookiePurgeAge[]       = "network.cookie.purgeAge";
     122             : static const char kPrefThirdPartySession[]    = "network.cookie.thirdparty.sessionOnly";
     123             : static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
     124             : 
     125             : // For telemetry COOKIE_LEAVE_SECURE_ALONE
     126             : #define BLOCKED_SECURE_SET_FROM_HTTP          0
     127             : #define BLOCKED_DOWNGRADE_SECURE_INEXACT      1
     128             : #define DOWNGRADE_SECURE_FROM_SECURE_INEXACT  2
     129             : #define EVICTED_NEWER_INSECURE                3
     130             : #define EVICTED_OLDEST_COOKIE                 4
     131             : #define EVICTED_PREFERRED_COOKIE              5
     132             : #define EVICTING_SECURE_BLOCKED               6
     133             : #define BLOCKED_DOWNGRADE_SECURE_EXACT        7
     134             : #define DOWNGRADE_SECURE_FROM_SECURE_EXACT    8
     135             : 
     136             : static void
     137             : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
     138             :                      const nsCookieKey &aKey,
     139             :                      const nsCookie *aCookie);
     140             : 
     141             : // struct for temporarily storing cookie attributes during header parsing
     142           0 : struct nsCookieAttributes
     143             : {
     144             :   nsAutoCString name;
     145             :   nsAutoCString value;
     146             :   nsAutoCString host;
     147             :   nsAutoCString path;
     148             :   nsAutoCString expires;
     149             :   nsAutoCString maxage;
     150             :   int64_t expiryTime;
     151             :   bool isSession;
     152             :   bool isSecure;
     153             :   bool isHttpOnly;
     154             : };
     155             : 
     156             : // stores the nsCookieEntry entryclass and an index into the cookie array
     157             : // within that entryclass, for purposes of storing an iteration state that
     158             : // points to a certain cookie.
     159             : struct nsListIter
     160             : {
     161             :   // default (non-initializing) constructor.
     162           0 :   nsListIter() = default;
     163             : 
     164             :   // explicit constructor to a given iterator state with entryclass 'aEntry'
     165             :   // and index 'aIndex'.
     166             :   explicit
     167           0 :   nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
     168           0 :    : entry(aEntry)
     169           0 :    , index(aIndex)
     170             :   {
     171           0 :   }
     172             : 
     173             :   // get the nsCookie * the iterator currently points to.
     174           0 :   nsCookie * Cookie() const
     175             :   {
     176           0 :     return entry->GetCookies()[index];
     177             :   }
     178             : 
     179             :   nsCookieEntry            *entry;
     180             :   nsCookieEntry::IndexType  index;
     181             : };
     182             : 
     183             : /******************************************************************************
     184             :  * Cookie logging handlers
     185             :  * used for logging in nsCookieService
     186             :  ******************************************************************************/
     187             : 
     188             : // logging handlers
     189             : #ifdef MOZ_LOGGING
     190             : // in order to do logging, the following environment variables need to be set:
     191             : //
     192             : //    set MOZ_LOG=cookie:3 -- shows rejected cookies
     193             : //    set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
     194             : //    set MOZ_LOG_FILE=cookie.log
     195             : //
     196             : #include "mozilla/Logging.h"
     197             : #endif
     198             : 
     199             : // define logging macros for convenience
     200             : #define SET_COOKIE true
     201             : #define GET_COOKIE false
     202             : 
     203             : static LazyLogModule gCookieLog("cookie");
     204             : 
     205             : #define COOKIE_LOGFAILURE(a, b, c, d)    LogFailure(a, b, c, d)
     206             : #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
     207             : 
     208             : #define COOKIE_LOGEVICTED(a, details)          \
     209             :   PR_BEGIN_MACRO                               \
     210             :   if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug))  \
     211             :       LogEvicted(a, details);                  \
     212             :   PR_END_MACRO
     213             : 
     214             : #define COOKIE_LOGSTRING(lvl, fmt)   \
     215             :   PR_BEGIN_MACRO                     \
     216             :     MOZ_LOG(gCookieLog, lvl, fmt);  \
     217             :     MOZ_LOG(gCookieLog, lvl, ("\n")); \
     218             :   PR_END_MACRO
     219             : 
     220             : static void
     221           0 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
     222             : {
     223             :   // if logging isn't enabled, return now to save cycles
     224           0 :   if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning))
     225           0 :     return;
     226             : 
     227           0 :   nsAutoCString spec;
     228           0 :   if (aHostURI)
     229           0 :     aHostURI->GetAsciiSpec(spec);
     230             : 
     231           0 :   MOZ_LOG(gCookieLog, LogLevel::Warning,
     232             :     ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
     233           0 :   MOZ_LOG(gCookieLog, LogLevel::Warning,("request URL: %s\n", spec.get()));
     234           0 :   if (aSetCookie)
     235           0 :     MOZ_LOG(gCookieLog, LogLevel::Warning,("cookie string: %s\n", aCookieString));
     236             : 
     237             :   PRExplodedTime explodedTime;
     238           0 :   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
     239             :   char timeString[40];
     240           0 :   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
     241             : 
     242           0 :   MOZ_LOG(gCookieLog, LogLevel::Warning,("current time: %s", timeString));
     243           0 :   MOZ_LOG(gCookieLog, LogLevel::Warning,("rejected because %s\n", aReason));
     244           0 :   MOZ_LOG(gCookieLog, LogLevel::Warning,("\n"));
     245             : }
     246             : 
     247             : static void
     248           0 : LogCookie(nsCookie *aCookie)
     249             : {
     250             :   PRExplodedTime explodedTime;
     251           0 :   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
     252             :   char timeString[40];
     253           0 :   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
     254             : 
     255           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("current time: %s", timeString));
     256             : 
     257           0 :   if (aCookie) {
     258           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("----------------\n"));
     259           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("name: %s\n", aCookie->Name().get()));
     260           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("value: %s\n", aCookie->Value().get()));
     261           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
     262           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("path: %s\n", aCookie->Path().get()));
     263             : 
     264           0 :     PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
     265           0 :                    PR_GMTParameters, &explodedTime);
     266           0 :     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
     267           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,
     268             :       ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
     269             : 
     270           0 :     PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
     271           0 :     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
     272           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("created: %s", timeString));
     273             : 
     274           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
     275           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
     276             : 
     277           0 :     nsAutoCString suffix;
     278           0 :     aCookie->OriginAttributesRef().CreateSuffix(suffix);
     279           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("origin attributes: %s\n",
     280             :             suffix.IsEmpty() ? "{empty}" : suffix.get()));
     281             :   }
     282           0 : }
     283             : 
     284             : static void
     285           0 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
     286             : {
     287             :   // if logging isn't enabled, return now to save cycles
     288           0 :   if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
     289           0 :     return;
     290             :   }
     291             : 
     292           0 :   nsAutoCString spec;
     293           0 :   if (aHostURI)
     294           0 :     aHostURI->GetAsciiSpec(spec);
     295             : 
     296           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,
     297             :     ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
     298           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("request URL: %s\n", spec.get()));
     299           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("cookie string: %s\n", aCookieString));
     300           0 :   if (aSetCookie)
     301           0 :     MOZ_LOG(gCookieLog, LogLevel::Debug,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
     302             : 
     303           0 :   LogCookie(aCookie);
     304             : 
     305           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
     306             : }
     307             : 
     308             : static void
     309           0 : LogEvicted(nsCookie *aCookie, const char* details)
     310             : {
     311           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("===== COOKIE EVICTED =====\n"));
     312           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("%s\n", details));
     313             : 
     314           0 :   LogCookie(aCookie);
     315             : 
     316           0 :   MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
     317           0 : }
     318             : 
     319             : // inline wrappers to make passing in nsCStrings easier
     320             : static inline void
     321           0 : LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, const char *aReason)
     322             : {
     323           0 :   LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
     324           0 : }
     325             : 
     326             : static inline void
     327           0 : LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, nsCookie *aCookie, bool aReplacing)
     328             : {
     329           0 :   LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
     330           0 : }
     331             : 
     332             : #ifdef DEBUG
     333             : #define NS_ASSERT_SUCCESS(res)                                               \
     334             :   PR_BEGIN_MACRO                                                             \
     335             :   nsresult __rv = res; /* Do not evaluate |res| more than once! */           \
     336             :   if (NS_FAILED(__rv)) {                                                     \
     337             :     SmprintfPointer msg = mozilla::Smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%" PRIX32, \
     338             :                            #res, static_cast<uint32_t>(__rv));               \
     339             :     NS_ASSERTION(NS_SUCCEEDED(__rv), msg.get());                             \
     340             :   }                                                                          \
     341             :   PR_END_MACRO
     342             : #else
     343             : #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
     344             : #endif
     345             : 
     346             : /******************************************************************************
     347             :  * DBListenerErrorHandler impl:
     348             :  * Parent class for our async storage listeners that handles the logging of
     349             :  * errors.
     350             :  ******************************************************************************/
     351           1 : class DBListenerErrorHandler : public mozIStorageStatementCallback
     352             : {
     353             : protected:
     354           4 :   explicit DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
     355             :   RefPtr<DBState> mDBState;
     356             :   virtual const char *GetOpType() = 0;
     357             : 
     358             : public:
     359           0 :   NS_IMETHOD HandleError(mozIStorageError* aError) override
     360             :   {
     361           0 :     if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
     362           0 :       int32_t result = -1;
     363           0 :       aError->GetResult(&result);
     364             : 
     365           0 :       nsAutoCString message;
     366           0 :       aError->GetMessage(message);
     367           0 :       COOKIE_LOGSTRING(LogLevel::Warning,
     368             :         ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
     369             :          "performing operation '%s' with message '%s'; rebuilding database.",
     370             :          result, GetOpType(), message.get()));
     371             :     }
     372             : 
     373             :     // Rebuild the database.
     374           0 :     gCookieService->HandleCorruptDB(mDBState);
     375             : 
     376           0 :     return NS_OK;
     377             :   }
     378             : };
     379             : 
     380             : /******************************************************************************
     381             :  * InsertCookieDBListener impl:
     382             :  * mozIStorageStatementCallback used to track asynchronous insertion operations.
     383             :  ******************************************************************************/
     384             : class InsertCookieDBListener final : public DBListenerErrorHandler
     385             : {
     386             : private:
     387           0 :   const char *GetOpType() override { return "INSERT"; }
     388             : 
     389           0 :   ~InsertCookieDBListener() = default;
     390             : 
     391             : public:
     392             :   NS_DECL_ISUPPORTS
     393             : 
     394           1 :   explicit InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
     395           0 :   NS_IMETHOD HandleResult(mozIStorageResultSet*) override
     396             :   {
     397           0 :     NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
     398           0 :     return NS_OK;
     399             :   }
     400           0 :   NS_IMETHOD HandleCompletion(uint16_t aReason) override
     401             :   {
     402             :     // If we were rebuilding the db and we succeeded, make our corruptFlag say
     403             :     // so.
     404           0 :     if (mDBState->corruptFlag == DBState::REBUILDING &&
     405             :         aReason == mozIStorageStatementCallback::REASON_FINISHED) {
     406           0 :       COOKIE_LOGSTRING(LogLevel::Debug,
     407             :         ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
     408           0 :       mDBState->corruptFlag = DBState::OK;
     409             :     }
     410           0 :     return NS_OK;
     411             :   }
     412             : };
     413             : 
     414           4 : NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
     415             : 
     416             : /******************************************************************************
     417             :  * UpdateCookieDBListener impl:
     418             :  * mozIStorageStatementCallback used to track asynchronous update operations.
     419             :  ******************************************************************************/
     420             : class UpdateCookieDBListener final : public DBListenerErrorHandler
     421             : {
     422             : private:
     423           0 :   const char *GetOpType() override { return "UPDATE"; }
     424             : 
     425           0 :   ~UpdateCookieDBListener() = default;
     426             : 
     427             : public:
     428             :   NS_DECL_ISUPPORTS
     429             : 
     430           1 :   explicit UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
     431           0 :   NS_IMETHOD HandleResult(mozIStorageResultSet*) override
     432             :   {
     433           0 :     NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
     434           0 :     return NS_OK;
     435             :   }
     436           0 :   NS_IMETHOD HandleCompletion(uint16_t aReason) override
     437             :   {
     438           0 :     return NS_OK;
     439             :   }
     440             : };
     441             : 
     442           4 : NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
     443             : 
     444             : /******************************************************************************
     445             :  * RemoveCookieDBListener impl:
     446             :  * mozIStorageStatementCallback used to track asynchronous removal operations.
     447             :  ******************************************************************************/
     448             : class RemoveCookieDBListener final : public DBListenerErrorHandler
     449             : {
     450             : private:
     451           0 :   const char *GetOpType() override { return "REMOVE"; }
     452             : 
     453           0 :   ~RemoveCookieDBListener() = default;
     454             : 
     455             : public:
     456             :   NS_DECL_ISUPPORTS
     457             : 
     458           1 :   explicit RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
     459           0 :   NS_IMETHOD HandleResult(mozIStorageResultSet*) override
     460             :   {
     461           0 :     NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
     462           0 :     return NS_OK;
     463             :   }
     464           1 :   NS_IMETHOD HandleCompletion(uint16_t aReason) override
     465             :   {
     466           1 :     return NS_OK;
     467             :   }
     468             : };
     469             : 
     470          12 : NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
     471             : 
     472             : /******************************************************************************
     473             :  * ReadCookieDBListener impl:
     474             :  * mozIStorageStatementCallback used to track asynchronous removal operations.
     475             :  ******************************************************************************/
     476             : class ReadCookieDBListener final : public DBListenerErrorHandler
     477             : {
     478             : private:
     479           0 :   const char *GetOpType() override { return "READ"; }
     480             :   bool mCanceled;
     481             : 
     482           1 :   ~ReadCookieDBListener() = default;
     483             : 
     484             : public:
     485             :   NS_DECL_ISUPPORTS
     486             : 
     487           1 :   explicit ReadCookieDBListener(DBState* dbState)
     488           1 :     : DBListenerErrorHandler(dbState)
     489           1 :     , mCanceled(false)
     490             :   {
     491           1 :   }
     492             : 
     493           0 :   void Cancel() { mCanceled = true; }
     494             : 
     495           0 :   NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) override
     496             :   {
     497           0 :     nsCOMPtr<mozIStorageRow> row;
     498             : 
     499             :     while (true) {
     500           0 :       DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
     501           0 :       NS_ASSERT_SUCCESS(rv);
     502             : 
     503           0 :       if (!row)
     504           0 :         break;
     505             : 
     506           0 :       CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
     507           0 :       row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
     508             : 
     509           0 :       nsAutoCString suffix;
     510           0 :       row->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
     511           0 :       DebugOnly<bool> success = tuple->key.mOriginAttributes.PopulateFromSuffix(suffix);
     512           0 :       MOZ_ASSERT(success);
     513             : 
     514             :       tuple->cookie =
     515           0 :         gCookieService->GetCookieFromRow(row, tuple->key.mOriginAttributes);
     516           0 :     }
     517             : 
     518           0 :     return NS_OK;
     519             :   }
     520           1 :   NS_IMETHOD HandleCompletion(uint16_t aReason) override
     521             :   {
     522             :     // Process the completion of the read operation. If we have been canceled,
     523             :     // we cannot assume that the cookieservice still has an open connection
     524             :     // or that it even refers to the same database, so we must return early.
     525             :     // Conversely, the cookieservice guarantees that if we have not been
     526             :     // canceled, the database connection is still alive and we can safely
     527             :     // operate on it.
     528             : 
     529           1 :     if (mCanceled) {
     530             :       // We may receive a REASON_FINISHED after being canceled;
     531             :       // tweak the reason accordingly.
     532           0 :       aReason = mozIStorageStatementCallback::REASON_CANCELED;
     533             :     }
     534             : 
     535           1 :     switch (aReason) {
     536             :     case mozIStorageStatementCallback::REASON_FINISHED:
     537           1 :       gCookieService->AsyncReadComplete();
     538           1 :       break;
     539             :     case mozIStorageStatementCallback::REASON_CANCELED:
     540             :       // Nothing more to do here. The partially read data has already been
     541             :       // thrown away.
     542           0 :       COOKIE_LOGSTRING(LogLevel::Debug, ("Read canceled"));
     543           0 :       break;
     544             :     case mozIStorageStatementCallback::REASON_ERROR:
     545             :       // Nothing more to do here. DBListenerErrorHandler::HandleError()
     546             :       // can handle it.
     547           0 :       COOKIE_LOGSTRING(LogLevel::Debug, ("Read error"));
     548           0 :       break;
     549             :     default:
     550           0 :       NS_NOTREACHED("invalid reason");
     551             :     }
     552           1 :     return NS_OK;
     553             :   }
     554             : };
     555             : 
     556           9 : NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
     557             : 
     558             : /******************************************************************************
     559             :  * CloseCookieDBListener imp:
     560             :  * Static mozIStorageCompletionCallback used to notify when the database is
     561             :  * successfully closed.
     562             :  ******************************************************************************/
     563             : class CloseCookieDBListener final :  public mozIStorageCompletionCallback
     564             : {
     565           0 :   ~CloseCookieDBListener() = default;
     566             : 
     567             : public:
     568           1 :   explicit CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
     569             :   RefPtr<DBState> mDBState;
     570             :   NS_DECL_ISUPPORTS
     571             : 
     572           0 :   NS_IMETHOD Complete(nsresult, nsISupports*) override
     573             :   {
     574           0 :     gCookieService->HandleDBClosed(mDBState);
     575           0 :     return NS_OK;
     576             :   }
     577             : };
     578             : 
     579           4 : NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
     580             : 
     581             : namespace {
     582             : 
     583           3 : class AppClearDataObserver final : public nsIObserver {
     584             : 
     585             :   ~AppClearDataObserver() = default;
     586             : 
     587             : public:
     588             :   NS_DECL_ISUPPORTS
     589             : 
     590             :   // nsIObserver implementation.
     591             :   NS_IMETHOD
     592           0 :   Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
     593             :   {
     594           0 :     MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA));
     595             : 
     596           0 :     MOZ_ASSERT(XRE_IsParentProcess());
     597             : 
     598             :     nsCOMPtr<nsICookieManager2> cookieManager
     599           0 :       = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
     600           0 :     MOZ_ASSERT(cookieManager);
     601             : 
     602           0 :     return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString());
     603             :   }
     604             : };
     605             : 
     606          18 : NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
     607             : 
     608             : } // namespace
     609             : 
     610             : size_t
     611           0 : nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     612             : {
     613           0 :   return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     614             : }
     615             : 
     616             : size_t
     617           0 : nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     618             : {
     619           0 :   size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
     620             : 
     621           0 :   amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
     622           0 :   for (uint32_t i = 0; i < mCookies.Length(); ++i) {
     623           0 :     amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
     624             :   }
     625             : 
     626           0 :   return amount;
     627             : }
     628             : 
     629             : size_t
     630           0 : CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     631             : {
     632           0 :   size_t amount = 0;
     633             : 
     634           0 :   amount += key.SizeOfExcludingThis(aMallocSizeOf);
     635           0 :   amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
     636             : 
     637           0 :   return amount;
     638             : }
     639             : 
     640             : size_t
     641           0 : DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
     642             : {
     643           0 :   size_t amount = 0;
     644             : 
     645           0 :   amount += aMallocSizeOf(this);
     646           0 :   amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
     647           0 :   amount += hostArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
     648           0 :   for (uint32_t i = 0; i < hostArray.Length(); ++i) {
     649           0 :     amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
     650             :   }
     651           0 :   amount += readSet.SizeOfExcludingThis(aMallocSizeOf);
     652             : 
     653           0 :   return amount;
     654             : }
     655             : 
     656             : /******************************************************************************
     657             :  * nsCookieService impl:
     658             :  * singleton instance ctor/dtor methods
     659             :  ******************************************************************************/
     660             : 
     661             : nsICookieService*
     662           1 : nsCookieService::GetXPCOMSingleton()
     663             : {
     664           1 :   if (IsNeckoChild())
     665           0 :     return CookieServiceChild::GetSingleton();
     666             : 
     667           1 :   return GetSingleton();
     668             : }
     669             : 
     670             : nsCookieService*
     671           1 : nsCookieService::GetSingleton()
     672             : {
     673           1 :   NS_ASSERTION(!IsNeckoChild(), "not a parent process");
     674             : 
     675           1 :   if (gCookieService) {
     676           0 :     NS_ADDREF(gCookieService);
     677           0 :     return gCookieService;
     678             :   }
     679             : 
     680             :   // Create a new singleton nsCookieService.
     681             :   // We AddRef only once since XPCOM has rules about the ordering of module
     682             :   // teardowns - by the time our module destructor is called, it's too late to
     683             :   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
     684             :   // cycles have already been completed and would result in serious leaks.
     685             :   // See bug 209571.
     686           1 :   gCookieService = new nsCookieService();
     687           1 :   if (gCookieService) {
     688           1 :     NS_ADDREF(gCookieService);
     689           1 :     if (NS_FAILED(gCookieService->Init())) {
     690           0 :       NS_RELEASE(gCookieService);
     691             :     }
     692             :   }
     693             : 
     694           1 :   return gCookieService;
     695             : }
     696             : 
     697             : /* static */ void
     698           3 : nsCookieService::AppClearDataObserverInit()
     699             : {
     700           6 :   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
     701           6 :   nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
     702           6 :   observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA,
     703           6 :                                /* ownsWeak= */ false);
     704           3 : }
     705             : 
     706             : /******************************************************************************
     707             :  * nsCookieService impl:
     708             :  * public methods
     709             :  ******************************************************************************/
     710             : 
     711          73 : NS_IMPL_ISUPPORTS(nsCookieService,
     712             :                   nsICookieService,
     713             :                   nsICookieManager,
     714             :                   nsICookieManager2,
     715             :                   nsIObserver,
     716             :                   nsISupportsWeakReference,
     717             :                   nsIMemoryReporter)
     718             : 
     719           1 : nsCookieService::nsCookieService()
     720             :  : mDBState(nullptr)
     721             :  , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
     722             :  , mThirdPartySession(false)
     723             :  , mLeaveSecureAlone(true)
     724             :  , mMaxNumberOfCookies(kMaxNumberOfCookies)
     725             :  , mMaxCookiesPerHost(kMaxCookiesPerHost)
     726           1 :  , mCookiePurgeAge(kCookiePurgeAge)
     727             : {
     728           1 : }
     729             : 
     730             : nsresult
     731           1 : nsCookieService::Init()
     732             : {
     733             :   nsresult rv;
     734           1 :   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
     735           1 :   NS_ENSURE_SUCCESS(rv, rv);
     736             : 
     737           1 :   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
     738           1 :   NS_ENSURE_SUCCESS(rv, rv);
     739             : 
     740           1 :   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
     741           1 :   NS_ENSURE_SUCCESS(rv, rv);
     742             : 
     743             :   // init our pref and observer
     744           2 :   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
     745           1 :   if (prefBranch) {
     746           1 :     prefBranch->AddObserver(kPrefCookieBehavior,        this, true);
     747           1 :     prefBranch->AddObserver(kPrefMaxNumberOfCookies,    this, true);
     748           1 :     prefBranch->AddObserver(kPrefMaxCookiesPerHost,     this, true);
     749           1 :     prefBranch->AddObserver(kPrefCookiePurgeAge,        this, true);
     750           1 :     prefBranch->AddObserver(kPrefThirdPartySession,     this, true);
     751           1 :     prefBranch->AddObserver(kCookieLeaveSecurityAlone,  this, true);
     752           1 :     PrefChanged(prefBranch);
     753             :   }
     754             : 
     755           1 :   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
     756           1 :   NS_ENSURE_SUCCESS(rv, rv);
     757             : 
     758             :   // Init our default, and possibly private DBStates.
     759           1 :   InitDBStates();
     760             : 
     761           1 :   RegisterWeakMemoryReporter(this);
     762             : 
     763           2 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     764           1 :   NS_ENSURE_STATE(os);
     765           1 :   os->AddObserver(this, "profile-before-change", true);
     766           1 :   os->AddObserver(this, "profile-do-change", true);
     767           1 :   os->AddObserver(this, "last-pb-context-exited", true);
     768             : 
     769           1 :   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
     770           1 :   if (!mPermissionService) {
     771           0 :     NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
     772           0 :     COOKIE_LOGSTRING(LogLevel::Warning, ("Init(): nsICookiePermission implementation not available"));
     773             :   }
     774             : 
     775           1 :   return NS_OK;
     776             : }
     777             : 
     778             : void
     779           1 : nsCookieService::InitDBStates()
     780             : {
     781           1 :   NS_ASSERTION(!mDBState, "already have a DBState");
     782           1 :   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
     783           1 :   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
     784             : 
     785             :   // Create a new default DBState and set our current one.
     786           1 :   mDefaultDBState = new DBState();
     787           1 :   mDBState = mDefaultDBState;
     788             : 
     789           1 :   mPrivateDBState = new DBState();
     790             : 
     791             :   // Get our cookie file.
     792           1 :   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
     793           2 :     getter_AddRefs(mDefaultDBState->cookieFile));
     794           1 :   if (NS_FAILED(rv)) {
     795             :     // We've already set up our DBStates appropriately; nothing more to do.
     796           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
     797             :       ("InitDBStates(): couldn't get cookie file"));
     798           0 :     return;
     799             :   }
     800           1 :   mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
     801             : 
     802             :   // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
     803             :   // do so.
     804           1 :   OpenDBResult result = TryInitDB(false);
     805           1 :   if (result == RESULT_RETRY) {
     806             :     // Database may be corrupt. Synchronously close the connection, clean up the
     807             :     // default DBState, and try again.
     808           0 :     COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
     809           0 :     CleanupCachedStatements();
     810           0 :     CleanupDefaultDBConnection();
     811           0 :     result = TryInitDB(true);
     812           0 :     if (result == RESULT_RETRY) {
     813             :       // We're done. Change the code to failure so we clean up below.
     814           0 :       result = RESULT_FAILURE;
     815             :     }
     816             :   }
     817             : 
     818           1 :   if (result == RESULT_FAILURE) {
     819           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
     820             :       ("InitDBStates(): TryInitDB() failed, closing connection"));
     821             : 
     822             :     // Connection failure is unrecoverable. Clean up our connection. We can run
     823             :     // fine without persistent storage -- e.g. if there's no profile.
     824           0 :     CleanupCachedStatements();
     825           0 :     CleanupDefaultDBConnection();
     826             :   }
     827             : }
     828             : 
     829             : namespace {
     830             : 
     831           0 : class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction
     832             : {
     833             :   ~ConvertAppIdToOriginAttrsSQLFunction() = default;
     834             : 
     835             :   NS_DECL_ISUPPORTS
     836             :   NS_DECL_MOZISTORAGEFUNCTION
     837             : };
     838             : 
     839           0 : NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
     840             : 
     841             : NS_IMETHODIMP
     842           0 : ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
     843             :   mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
     844             : {
     845             :   nsresult rv;
     846             :   int32_t inIsolatedMozBrowser;
     847             : 
     848           0 :   rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
     849           0 :   NS_ENSURE_SUCCESS(rv, rv);
     850             : 
     851             :   // Create an originAttributes object by inIsolatedMozBrowser.
     852             :   // Then create the originSuffix string from this object.
     853             :   OriginAttributes attrs(nsIScriptSecurityManager::NO_APP_ID,
     854           0 :                          (inIsolatedMozBrowser ? true : false));
     855           0 :   nsAutoCString suffix;
     856           0 :   attrs.CreateSuffix(suffix);
     857             : 
     858           0 :   RefPtr<nsVariant> outVar(new nsVariant());
     859           0 :   rv = outVar->SetAsAUTF8String(suffix);
     860           0 :   NS_ENSURE_SUCCESS(rv, rv);
     861             : 
     862           0 :   outVar.forget(aResult);
     863           0 :   return NS_OK;
     864             : }
     865             : 
     866           0 : class SetAppIdFromOriginAttributesSQLFunction final : public mozIStorageFunction
     867             : {
     868             :   ~SetAppIdFromOriginAttributesSQLFunction() = default;
     869             : 
     870             :   NS_DECL_ISUPPORTS
     871             :   NS_DECL_MOZISTORAGEFUNCTION
     872             : };
     873             : 
     874           0 : NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
     875             : 
     876             : NS_IMETHODIMP
     877           0 : SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
     878             :   mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
     879             : {
     880             :   nsresult rv;
     881           0 :   nsAutoCString suffix;
     882           0 :   OriginAttributes attrs;
     883             : 
     884           0 :   rv = aFunctionArguments->GetUTF8String(0, suffix);
     885           0 :   NS_ENSURE_SUCCESS(rv, rv);
     886           0 :   bool success = attrs.PopulateFromSuffix(suffix);
     887           0 :   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
     888             : 
     889           0 :   RefPtr<nsVariant> outVar(new nsVariant());
     890           0 :   rv = outVar->SetAsInt32(attrs.mAppId);
     891           0 :   NS_ENSURE_SUCCESS(rv, rv);
     892             : 
     893           0 :   outVar.forget(aResult);
     894           0 :   return NS_OK;
     895             : }
     896             : 
     897           0 : class SetInBrowserFromOriginAttributesSQLFunction final :
     898             :   public mozIStorageFunction
     899             : {
     900             :   ~SetInBrowserFromOriginAttributesSQLFunction() = default;
     901             : 
     902             :   NS_DECL_ISUPPORTS
     903             :   NS_DECL_MOZISTORAGEFUNCTION
     904             : };
     905             : 
     906           0 : NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
     907             :                   mozIStorageFunction);
     908             : 
     909             : NS_IMETHODIMP
     910           0 : SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
     911             :   mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
     912             : {
     913             :   nsresult rv;
     914           0 :   nsAutoCString suffix;
     915           0 :   OriginAttributes attrs;
     916             : 
     917           0 :   rv = aFunctionArguments->GetUTF8String(0, suffix);
     918           0 :   NS_ENSURE_SUCCESS(rv, rv);
     919           0 :   bool success = attrs.PopulateFromSuffix(suffix);
     920           0 :   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
     921             : 
     922           0 :   RefPtr<nsVariant> outVar(new nsVariant());
     923           0 :   rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
     924           0 :   NS_ENSURE_SUCCESS(rv, rv);
     925             : 
     926           0 :   outVar.forget(aResult);
     927           0 :   return NS_OK;
     928             : }
     929             : 
     930             : } // namespace
     931             : 
     932             : /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
     933             :  * move the existing database file out of the way and create a new one.
     934             :  *
     935             :  * @returns RESULT_OK if opening or creating the database succeeded;
     936             :  *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
     937             :  *          other failure occurred that might be resolved by recreating the
     938             :  *          database; or RESULT_FAILED if there was an unrecoverable error and
     939             :  *          we must run without a database.
     940             :  *
     941             :  * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
     942             :  * cleanup of the default DBState.
     943             :  */
     944             : OpenDBResult
     945           1 : nsCookieService::TryInitDB(bool aRecreateDB)
     946             : {
     947           1 :   NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
     948           1 :   NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
     949           1 :   NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
     950           1 :   NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
     951             : 
     952             :   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
     953             :   // want to delete it outright, since it may be useful for debugging purposes,
     954             :   // so we move it out of the way.
     955             :   nsresult rv;
     956           1 :   if (aRecreateDB) {
     957           0 :     nsCOMPtr<nsIFile> backupFile;
     958           0 :     mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
     959           0 :     rv = backupFile->MoveToNative(nullptr,
     960           0 :       NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
     961           0 :     NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
     962             :   }
     963             : 
     964             :   // This block provides scope for the Telemetry AutoTimer
     965             :   {
     966             :     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
     967           2 :       telemetry;
     968           1 :     ReadAheadFile(mDefaultDBState->cookieFile);
     969             : 
     970             :     // open a connection to the cookie database, and only cache our connection
     971             :     // and statements upon success. The connection is opened unshared to eliminate
     972             :     // cache contention between the main and background threads.
     973           2 :     rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
     974           2 :       getter_AddRefs(mDefaultDBState->dbConn));
     975           1 :     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
     976             :   }
     977             : 
     978             :   // Set up our listeners.
     979           2 :   mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
     980           2 :   mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
     981           2 :   mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
     982           2 :   mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
     983             : 
     984             :   // Grow cookie db in 512KB increments
     985           1 :   mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
     986             : 
     987           1 :   bool tableExists = false;
     988           4 :   mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
     989           3 :     &tableExists);
     990           1 :   if (!tableExists) {
     991           0 :     rv = CreateTable();
     992           0 :     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
     993             : 
     994             :   } else {
     995             :     // table already exists; check the schema version before reading
     996             :     int32_t dbSchemaVersion;
     997           1 :     rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
     998           1 :     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
     999             : 
    1000             :     // Start a transaction for the whole migration block.
    1001           2 :     mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
    1002             : 
    1003           1 :     switch (dbSchemaVersion) {
    1004             :     // Upgrading.
    1005             :     // Every time you increment the database schema, you need to implement
    1006             :     // the upgrading code from the previous version to the new one. If migration
    1007             :     // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
    1008             :     // the original database will be saved, in the hopes that we might one day
    1009             :     // see it and fix it.
    1010             :     case 1:
    1011             :       {
    1012             :         // Add the lastAccessed column to the table.
    1013           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1014           0 :           "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
    1015           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1016             :       }
    1017             :       // Fall through to the next upgrade.
    1018             :       MOZ_FALLTHROUGH;
    1019             : 
    1020             :     case 2:
    1021             :       {
    1022             :         // Add the baseDomain column and index to the table.
    1023           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1024           0 :           "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
    1025           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1026             : 
    1027             :         // Compute the baseDomains for the table. This must be done eagerly
    1028             :         // otherwise we won't be able to synchronously read in individual
    1029             :         // domains on demand.
    1030           0 :         const int64_t SCHEMA2_IDX_ID  =  0;
    1031           0 :         const int64_t SCHEMA2_IDX_HOST = 1;
    1032           0 :         nsCOMPtr<mozIStorageStatement> select;
    1033           0 :         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
    1034           0 :           "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
    1035           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1036             : 
    1037           0 :         nsCOMPtr<mozIStorageStatement> update;
    1038           0 :         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
    1039             :           "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
    1040           0 :           getter_AddRefs(update));
    1041           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1042             : 
    1043           0 :         nsCString baseDomain, host;
    1044             :         bool hasResult;
    1045             :         while (true) {
    1046           0 :           rv = select->ExecuteStep(&hasResult);
    1047           0 :           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1048             : 
    1049           0 :           if (!hasResult)
    1050           0 :             break;
    1051             : 
    1052           0 :           int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
    1053           0 :           select->GetUTF8String(SCHEMA2_IDX_HOST, host);
    1054             : 
    1055           0 :           rv = GetBaseDomainFromHost(host, baseDomain);
    1056           0 :           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1057             : 
    1058           0 :           mozStorageStatementScoper scoper(update);
    1059             : 
    1060           0 :           rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
    1061           0 :                                             baseDomain);
    1062           0 :           NS_ASSERT_SUCCESS(rv);
    1063           0 :           rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
    1064           0 :                                        id);
    1065           0 :           NS_ASSERT_SUCCESS(rv);
    1066             : 
    1067           0 :           rv = update->ExecuteStep(&hasResult);
    1068           0 :           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1069           0 :         }
    1070             : 
    1071             :         // Create an index on baseDomain.
    1072           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1073           0 :           "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
    1074           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1075             :       }
    1076             :       // Fall through to the next upgrade.
    1077             :       MOZ_FALLTHROUGH;
    1078             : 
    1079             :     case 3:
    1080             :       {
    1081             :         // Add the creationTime column to the table, and create a unique index
    1082             :         // on (name, host, path). Before we do this, we have to purge the table
    1083             :         // of expired cookies such that we know that the (name, host, path)
    1084             :         // index is truly unique -- otherwise we can't create the index. Note
    1085             :         // that we can't just execute a statement to delete all rows where the
    1086             :         // expiry column is in the past -- doing so would rely on the clock
    1087             :         // (both now and when previous cookies were set) being monotonic.
    1088             : 
    1089             :         // Select the whole table, and order by the fields we're interested in.
    1090             :         // This means we can simply do a linear traversal of the results and
    1091             :         // check for duplicates as we go.
    1092           0 :         const int64_t SCHEMA3_IDX_ID =   0;
    1093           0 :         const int64_t SCHEMA3_IDX_NAME = 1;
    1094           0 :         const int64_t SCHEMA3_IDX_HOST = 2;
    1095           0 :         const int64_t SCHEMA3_IDX_PATH = 3;
    1096           0 :         nsCOMPtr<mozIStorageStatement> select;
    1097           0 :         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
    1098             :           "SELECT id, name, host, path FROM moz_cookies "
    1099             :             "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
    1100           0 :           getter_AddRefs(select));
    1101           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1102             : 
    1103           0 :         nsCOMPtr<mozIStorageStatement> deleteExpired;
    1104           0 :         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
    1105             :           "DELETE FROM moz_cookies WHERE id = :id"),
    1106           0 :           getter_AddRefs(deleteExpired));
    1107           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1108             : 
    1109             :         // Read the first row.
    1110             :         bool hasResult;
    1111           0 :         rv = select->ExecuteStep(&hasResult);
    1112           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1113             : 
    1114           0 :         if (hasResult) {
    1115           0 :           nsCString name1, host1, path1;
    1116           0 :           int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
    1117           0 :           select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
    1118           0 :           select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
    1119           0 :           select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
    1120             : 
    1121           0 :           nsCString name2, host2, path2;
    1122             :           while (true) {
    1123             :             // Read the second row.
    1124           0 :             rv = select->ExecuteStep(&hasResult);
    1125           0 :             NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1126             : 
    1127           0 :             if (!hasResult)
    1128           0 :               break;
    1129             : 
    1130           0 :             int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
    1131           0 :             select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
    1132           0 :             select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
    1133           0 :             select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
    1134             : 
    1135             :             // If the two rows match in (name, host, path), we know the earlier
    1136             :             // row has an earlier expiry time. Delete it.
    1137           0 :             if (name1 == name2 && host1 == host2 && path1 == path2) {
    1138           0 :               mozStorageStatementScoper scoper(deleteExpired);
    1139             : 
    1140           0 :               rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
    1141           0 :                 id1);
    1142           0 :               NS_ASSERT_SUCCESS(rv);
    1143             : 
    1144           0 :               rv = deleteExpired->ExecuteStep(&hasResult);
    1145           0 :               NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1146             :             }
    1147             : 
    1148             :             // Make the second row the first for the next iteration.
    1149           0 :             name1 = name2;
    1150           0 :             host1 = host2;
    1151           0 :             path1 = path2;
    1152           0 :             id1 = id2;
    1153           0 :           }
    1154             :         }
    1155             : 
    1156             :         // Add the creationTime column to the table.
    1157           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1158           0 :           "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
    1159           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1160             : 
    1161             :         // Copy the id of each row into the new creationTime column.
    1162           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1163             :           "UPDATE moz_cookies SET creationTime = "
    1164           0 :             "(SELECT id WHERE id = moz_cookies.id)"));
    1165           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1166             : 
    1167             :         // Create a unique index on (name, host, path) to allow fast lookup.
    1168           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1169             :           "CREATE UNIQUE INDEX moz_uniqueid "
    1170           0 :           "ON moz_cookies (name, host, path)"));
    1171           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1172             :       }
    1173             :       // Fall through to the next upgrade.
    1174             :       MOZ_FALLTHROUGH;
    1175             : 
    1176             :     case 4:
    1177             :       {
    1178             :         // We need to add appId/inBrowserElement, plus change a constraint on
    1179             :         // the table (unique entries now include appId/inBrowserElement):
    1180             :         // this requires creating a new table and copying the data to it.  We
    1181             :         // then rename the new table to the old name.
    1182             :         //
    1183             :         // Why we made this change: appId/inBrowserElement allow "cookie jars"
    1184             :         // for Firefox OS. We create a separate cookie namespace per {appId,
    1185             :         // inBrowserElement}.  When upgrading, we convert existing cookies
    1186             :         // (which imply we're on desktop/mobile) to use {0, false}, as that is
    1187             :         // the only namespace used by a non-Firefox-OS implementation.
    1188             : 
    1189             :         // Rename existing table
    1190           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1191           0 :           "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
    1192           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1193             : 
    1194             :         // Drop existing index (CreateTable will create new one for new table)
    1195           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1196           0 :           "DROP INDEX moz_basedomain"));
    1197           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1198             : 
    1199             :         // Create new table (with new fields and new unique constraint)
    1200           0 :         rv = CreateTableForSchemaVersion5();
    1201           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1202             : 
    1203             :         // Copy data from old table, using appId/inBrowser=0 for existing rows
    1204           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1205             :           "INSERT INTO moz_cookies "
    1206             :           "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
    1207             :           " lastAccessed, creationTime, isSecure, isHttpOnly) "
    1208             :           "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
    1209             :           " lastAccessed, creationTime, isSecure, isHttpOnly "
    1210           0 :           "FROM moz_cookies_old"));
    1211           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1212             : 
    1213             :         // Drop old table
    1214           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1215           0 :           "DROP TABLE moz_cookies_old"));
    1216           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1217             : 
    1218           0 :         COOKIE_LOGSTRING(LogLevel::Debug,
    1219             :           ("Upgraded database to schema version 5"));
    1220             :       }
    1221             :       // Fall through to the next upgrade.
    1222             :       MOZ_FALLTHROUGH;
    1223             : 
    1224             :     case 5:
    1225             :       {
    1226             :         // Change in the version: Replace the columns |appId| and
    1227             :         // |inBrowserElement| by a single column |originAttributes|.
    1228             :         //
    1229             :         // Why we made this change: FxOS new security model (NSec) encapsulates
    1230             :         // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to make
    1231             :         // it easier to modify the contents of this structure in the future.
    1232             :         //
    1233             :         // We do the migration in several steps:
    1234             :         // 1. Rename the old table.
    1235             :         // 2. Create a new table.
    1236             :         // 3. Copy data from the old table to the new table; convert appId and
    1237             :         //    inBrowserElement to originAttributes in the meantime.
    1238             : 
    1239             :         // Rename existing table.
    1240           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1241           0 :              "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
    1242           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1243             : 
    1244             :         // Drop existing index (CreateTable will create new one for new table).
    1245           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1246           0 :              "DROP INDEX moz_basedomain"));
    1247           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1248             : 
    1249             :         // Create new table with new fields and new unique constraint.
    1250           0 :         rv = CreateTableForSchemaVersion6();
    1251           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1252             : 
    1253             :         // Copy data from old table without the two deprecated columns appId and
    1254             :         // inBrowserElement.
    1255             :         nsCOMPtr<mozIStorageFunction>
    1256           0 :           convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction());
    1257           0 :         NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
    1258             : 
    1259           0 :         NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
    1260             :                                  "CONVERT_TO_ORIGIN_ATTRIBUTES");
    1261             : 
    1262           0 :         rv = mDefaultDBState->dbConn->CreateFunction(convertToOriginAttrsName,
    1263           0 :                                                      2, convertToOriginAttrs);
    1264           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1265             : 
    1266           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1267             :           "INSERT INTO moz_cookies "
    1268             :           "(baseDomain, originAttributes, name, value, host, path, expiry,"
    1269             :           " lastAccessed, creationTime, isSecure, isHttpOnly) "
    1270             :           "SELECT baseDomain, "
    1271             :           " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
    1272             :           " name, value, host, path, expiry, lastAccessed, creationTime, "
    1273             :           " isSecure, isHttpOnly "
    1274           0 :           "FROM moz_cookies_old"));
    1275           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1276             : 
    1277           0 :         rv = mDefaultDBState->dbConn->RemoveFunction(convertToOriginAttrsName);
    1278           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1279             : 
    1280             :         // Drop old table
    1281           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1282           0 :              "DROP TABLE moz_cookies_old"));
    1283           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1284             : 
    1285           0 :         COOKIE_LOGSTRING(LogLevel::Debug,
    1286             :           ("Upgraded database to schema version 6"));
    1287             :       }
    1288             :       MOZ_FALLTHROUGH;
    1289             : 
    1290             :     case 6:
    1291             :       {
    1292             :         // We made a mistake in schema version 6. We cannot remove expected
    1293             :         // columns of any version (checked in the default case) from cookie
    1294             :         // database, because doing this would destroy the possibility of
    1295             :         // downgrading database.
    1296             :         //
    1297             :         // This version simply restores appId and inBrowserElement columns in
    1298             :         // order to fix downgrading issue even though these two columns are no
    1299             :         // longer used in the latest schema.
    1300           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1301           0 :           "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
    1302           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1303             : 
    1304           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1305           0 :           "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
    1306           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1307             : 
    1308             :         // Compute and populate the values of appId and inBrwoserElement from
    1309             :         // originAttributes.
    1310             :         nsCOMPtr<mozIStorageFunction>
    1311           0 :           setAppId(new SetAppIdFromOriginAttributesSQLFunction());
    1312           0 :         NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
    1313             : 
    1314           0 :         NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
    1315             : 
    1316           0 :         rv = mDefaultDBState->dbConn->CreateFunction(setAppIdName, 1, setAppId);
    1317           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1318             : 
    1319             :         nsCOMPtr<mozIStorageFunction>
    1320           0 :           setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
    1321           0 :         NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
    1322             : 
    1323           0 :         NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
    1324             : 
    1325           0 :         rv = mDefaultDBState->dbConn->CreateFunction(setInBrowserName, 1,
    1326           0 :                                                      setInBrowser);
    1327           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1328             : 
    1329           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1330             :           "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
    1331             :           "inBrowserElement = SET_IN_BROWSER(originAttributes);"
    1332           0 :         ));
    1333           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1334             : 
    1335           0 :         rv = mDefaultDBState->dbConn->RemoveFunction(setAppIdName);
    1336           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1337             : 
    1338           0 :         rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName);
    1339           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1340             : 
    1341           0 :         COOKIE_LOGSTRING(LogLevel::Debug,
    1342             :           ("Upgraded database to schema version 7"));
    1343             :       }
    1344             :       MOZ_FALLTHROUGH;
    1345             : 
    1346             :     case 7:
    1347             :       {
    1348             :         // Remove the appId field from moz_cookies.
    1349             :         //
    1350             :         // Unfortunately sqlite doesn't support dropping columns using ALTER
    1351             :         // TABLE, so we need to go through the procedure documented in
    1352             :         // https://www.sqlite.org/lang_altertable.html.
    1353             : 
    1354             :         // Drop existing index
    1355           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1356           0 :              "DROP INDEX moz_basedomain"));
    1357           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1358             : 
    1359             :         // Create a new_moz_cookies table without the appId field.
    1360           0 :         rv = CreateTableWorker("new_moz_cookies");
    1361           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1362             : 
    1363             :         // Move the data over.
    1364           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1365             :           "INSERT INTO new_moz_cookies ("
    1366             :               "id, "
    1367             :               "baseDomain, "
    1368             :               "originAttributes, "
    1369             :               "name, "
    1370             :               "value, "
    1371             :               "host, "
    1372             :               "path, "
    1373             :               "expiry, "
    1374             :               "lastAccessed, "
    1375             :               "creationTime, "
    1376             :               "isSecure, "
    1377             :               "isHttpOnly, "
    1378             :               "inBrowserElement "
    1379             :             ") SELECT "
    1380             :               "id, "
    1381             :               "baseDomain, "
    1382             :               "originAttributes, "
    1383             :               "name, "
    1384             :               "value, "
    1385             :               "host, "
    1386             :               "path, "
    1387             :               "expiry, "
    1388             :               "lastAccessed, "
    1389             :               "creationTime, "
    1390             :               "isSecure, "
    1391             :               "isHttpOnly, "
    1392             :               "inBrowserElement "
    1393           0 :             "FROM moz_cookies;"));
    1394           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1395             : 
    1396             :         // Drop the old table
    1397           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1398           0 :           "DROP TABLE moz_cookies;"));
    1399           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1400             : 
    1401             :         // Rename new_moz_cookies to moz_cookies.
    1402           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1403           0 :           "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
    1404           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1405             : 
    1406             :         // Recreate our index.
    1407           0 :         rv = CreateIndex();
    1408           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1409             : 
    1410           0 :         COOKIE_LOGSTRING(LogLevel::Debug,
    1411             :           ("Upgraded database to schema version 8"));
    1412             :       }
    1413             : 
    1414             :       // No more upgrades. Update the schema version.
    1415           0 :       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
    1416           0 :       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1417             :       MOZ_FALLTHROUGH;
    1418             : 
    1419             :     case COOKIES_SCHEMA_VERSION:
    1420           1 :       break;
    1421             : 
    1422             :     case 0:
    1423             :       {
    1424           0 :         NS_WARNING("couldn't get schema version!");
    1425             : 
    1426             :         // the table may be usable; someone might've just clobbered the schema
    1427             :         // version. we can treat this case like a downgrade using the codepath
    1428             :         // below, by verifying the columns we care about are all there. for now,
    1429             :         // re-set the schema version in the db, in case the checks succeed (if
    1430             :         // they don't, we're dropping the table anyway).
    1431           0 :         rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
    1432           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1433             :       }
    1434             :       // fall through to downgrade check
    1435             :       MOZ_FALLTHROUGH;
    1436             : 
    1437             :     // downgrading.
    1438             :     // if columns have been added to the table, we can still use the ones we
    1439             :     // understand safely. if columns have been deleted or altered, just
    1440             :     // blow away the table and start from scratch! if you change the way
    1441             :     // a column is interpreted, make sure you also change its name so this
    1442             :     // check will catch it.
    1443             :     default:
    1444             :       {
    1445             :         // check if all the expected columns exist
    1446           0 :         nsCOMPtr<mozIStorageStatement> stmt;
    1447           0 :         rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
    1448             :           "SELECT "
    1449             :             "id, "
    1450             :             "baseDomain, "
    1451             :             "originAttributes, "
    1452             :             "name, "
    1453             :             "value, "
    1454             :             "host, "
    1455             :             "path, "
    1456             :             "expiry, "
    1457             :             "lastAccessed, "
    1458             :             "creationTime, "
    1459             :             "isSecure, "
    1460             :             "isHttpOnly "
    1461           0 :           "FROM moz_cookies"), getter_AddRefs(stmt));
    1462           0 :         if (NS_SUCCEEDED(rv))
    1463           0 :           break;
    1464             : 
    1465             :         // our columns aren't there - drop the table!
    1466           0 :         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1467           0 :           "DROP TABLE moz_cookies"));
    1468           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1469             : 
    1470           0 :         rv = CreateTable();
    1471           0 :         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1472             :       }
    1473           0 :       break;
    1474             :     }
    1475             :   }
    1476             : 
    1477             :   // make operations on the table asynchronous, for performance
    1478           4 :   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1479           3 :     "PRAGMA synchronous = OFF"));
    1480             : 
    1481             :   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
    1482             :   // 16 pages (around 500KB).
    1483           4 :   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1484           3 :     MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
    1485           4 :   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1486           3 :     "PRAGMA wal_autocheckpoint = 16"));
    1487             : 
    1488             :   // cache frequently used statements (for insertion, deletion, and updating)
    1489           4 :   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    1490             :     "INSERT INTO moz_cookies ("
    1491             :       "baseDomain, "
    1492             :       "originAttributes, "
    1493             :       "name, "
    1494             :       "value, "
    1495             :       "host, "
    1496             :       "path, "
    1497             :       "expiry, "
    1498             :       "lastAccessed, "
    1499             :       "creationTime, "
    1500             :       "isSecure, "
    1501             :       "isHttpOnly"
    1502             :     ") VALUES ("
    1503             :       ":baseDomain, "
    1504             :       ":originAttributes, "
    1505             :       ":name, "
    1506             :       ":value, "
    1507             :       ":host, "
    1508             :       ":path, "
    1509             :       ":expiry, "
    1510             :       ":lastAccessed, "
    1511             :       ":creationTime, "
    1512             :       ":isSecure, "
    1513             :       ":isHttpOnly"
    1514             :     ")"),
    1515           4 :     getter_AddRefs(mDefaultDBState->stmtInsert));
    1516           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1517             : 
    1518           4 :   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    1519             :     "DELETE FROM moz_cookies "
    1520             :     "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
    1521           4 :     getter_AddRefs(mDefaultDBState->stmtDelete));
    1522           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1523             : 
    1524           4 :   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    1525             :     "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
    1526             :     "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"),
    1527           4 :     getter_AddRefs(mDefaultDBState->stmtUpdate));
    1528           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    1529             : 
    1530             :   // if we deleted a corrupt db, don't attempt to import - return now
    1531           1 :   if (aRecreateDB)
    1532           0 :     return RESULT_OK;
    1533             : 
    1534             :   // check whether to import or just read in the db
    1535           1 :   if (tableExists)
    1536           1 :     return Read();
    1537             : 
    1538           0 :   nsCOMPtr<nsIFile> oldCookieFile;
    1539           0 :   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
    1540           0 :     getter_AddRefs(oldCookieFile));
    1541           0 :   if (NS_FAILED(rv)) return RESULT_OK;
    1542             : 
    1543             :   // Import cookies, and clean up the old file regardless of success or failure.
    1544             :   // Note that we have to switch out our DBState temporarily, in case we're in
    1545             :   // private browsing mode; otherwise ImportCookies() won't be happy.
    1546           0 :   DBState* initialState = mDBState;
    1547           0 :   mDBState = mDefaultDBState;
    1548           0 :   oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
    1549           0 :   ImportCookies(oldCookieFile);
    1550           0 :   oldCookieFile->Remove(false);
    1551           0 :   mDBState = initialState;
    1552             : 
    1553           0 :   return RESULT_OK;
    1554             : }
    1555             : 
    1556             : // Sets the schema version and creates the moz_cookies table.
    1557             : nsresult
    1558           0 : nsCookieService::CreateTableWorker(const char* aName)
    1559             : {
    1560             :   // Create the table.
    1561             :   // We default originAttributes to empty string: this is so if users revert to
    1562             :   // an older Firefox version that doesn't know about this field, any cookies
    1563             :   // set will still work once they upgrade back.
    1564           0 :   nsAutoCString command("CREATE TABLE ");
    1565           0 :   command.Append(aName);
    1566             :   command.Append(" ("
    1567             :       "id INTEGER PRIMARY KEY, "
    1568             :       "baseDomain TEXT, "
    1569             :       "originAttributes TEXT NOT NULL DEFAULT '', "
    1570             :       "name TEXT, "
    1571             :       "value TEXT, "
    1572             :       "host TEXT, "
    1573             :       "path TEXT, "
    1574             :       "expiry INTEGER, "
    1575             :       "lastAccessed INTEGER, "
    1576             :       "creationTime INTEGER, "
    1577             :       "isSecure INTEGER, "
    1578             :       "isHttpOnly INTEGER, "
    1579             :       "inBrowserElement INTEGER DEFAULT 0, "
    1580             :       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
    1581           0 :     ")");
    1582           0 :   return mDefaultDBState->dbConn->ExecuteSimpleSQL(command);
    1583             : }
    1584             : 
    1585             : // Sets the schema version and creates the moz_cookies table.
    1586             : nsresult
    1587           0 : nsCookieService::CreateTable()
    1588             : {
    1589             :   // Set the schema version, before creating the table.
    1590           0 :   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
    1591           0 :     COOKIES_SCHEMA_VERSION);
    1592           0 :   if (NS_FAILED(rv)) return rv;
    1593             : 
    1594           0 :   rv = CreateTableWorker("moz_cookies");
    1595           0 :   if (NS_FAILED(rv)) return rv;
    1596             : 
    1597           0 :   return CreateIndex();
    1598             : }
    1599             : 
    1600             : nsresult
    1601           0 : nsCookieService::CreateIndex()
    1602             : {
    1603             :   // Create an index on baseDomain.
    1604           0 :   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1605             :     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
    1606           0 :                                                 "originAttributes)"));
    1607             : }
    1608             : 
    1609             : // Sets the schema version and creates the moz_cookies table.
    1610             : nsresult
    1611           0 : nsCookieService::CreateTableForSchemaVersion6()
    1612             : {
    1613             :   // Set the schema version, before creating the table.
    1614           0 :   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(6);
    1615           0 :   if (NS_FAILED(rv)) return rv;
    1616             : 
    1617             :   // Create the table.
    1618             :   // We default originAttributes to empty string: this is so if users revert to
    1619             :   // an older Firefox version that doesn't know about this field, any cookies
    1620             :   // set will still work once they upgrade back.
    1621           0 :   rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1622             :     "CREATE TABLE moz_cookies ("
    1623             :       "id INTEGER PRIMARY KEY, "
    1624             :       "baseDomain TEXT, "
    1625             :       "originAttributes TEXT NOT NULL DEFAULT '', "
    1626             :       "name TEXT, "
    1627             :       "value TEXT, "
    1628             :       "host TEXT, "
    1629             :       "path TEXT, "
    1630             :       "expiry INTEGER, "
    1631             :       "lastAccessed INTEGER, "
    1632             :       "creationTime INTEGER, "
    1633             :       "isSecure INTEGER, "
    1634             :       "isHttpOnly INTEGER, "
    1635             :       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
    1636           0 :     ")"));
    1637           0 :   if (NS_FAILED(rv)) return rv;
    1638             : 
    1639             :   // Create an index on baseDomain.
    1640           0 :   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1641             :     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
    1642           0 :                                                 "originAttributes)"));
    1643             : }
    1644             : 
    1645             : // Sets the schema version and creates the moz_cookies table.
    1646             : nsresult
    1647           0 : nsCookieService::CreateTableForSchemaVersion5()
    1648             : {
    1649             :   // Set the schema version, before creating the table.
    1650           0 :   nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(5);
    1651           0 :   if (NS_FAILED(rv)) return rv;
    1652             : 
    1653             :   // Create the table. We default appId/inBrowserElement to 0: this is so if
    1654             :   // users revert to an older Firefox version that doesn't know about these
    1655             :   // fields, any cookies set will still work once they upgrade back.
    1656           0 :   rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1657             :     "CREATE TABLE moz_cookies ("
    1658             :       "id INTEGER PRIMARY KEY, "
    1659             :       "baseDomain TEXT, "
    1660             :       "appId INTEGER DEFAULT 0, "
    1661             :       "inBrowserElement INTEGER DEFAULT 0, "
    1662             :       "name TEXT, "
    1663             :       "value TEXT, "
    1664             :       "host TEXT, "
    1665             :       "path TEXT, "
    1666             :       "expiry INTEGER, "
    1667             :       "lastAccessed INTEGER, "
    1668             :       "creationTime INTEGER, "
    1669             :       "isSecure INTEGER, "
    1670             :       "isHttpOnly INTEGER, "
    1671             :       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
    1672           0 :     ")"));
    1673           0 :   if (NS_FAILED(rv)) return rv;
    1674             : 
    1675             :   // Create an index on baseDomain.
    1676           0 :   return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    1677             :     "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
    1678             :                                                 "appId, "
    1679           0 :                                                 "inBrowserElement)"));
    1680             : }
    1681             : 
    1682             : void
    1683           0 : nsCookieService::CloseDBStates()
    1684             : {
    1685             :   // Null out our private and pointer DBStates regardless.
    1686           0 :   mPrivateDBState = nullptr;
    1687           0 :   mDBState = nullptr;
    1688             : 
    1689             :   // If we don't have a default DBState, we're done.
    1690           0 :   if (!mDefaultDBState)
    1691           0 :     return;
    1692             : 
    1693             :   // Cleanup cached statements before we can close anything.
    1694           0 :   CleanupCachedStatements();
    1695             : 
    1696           0 :   if (mDefaultDBState->dbConn) {
    1697             :     // Cancel any pending read. No further results will be received by our
    1698             :     // read listener.
    1699           0 :     if (mDefaultDBState->pendingRead) {
    1700           0 :       CancelAsyncRead(true);
    1701             :     }
    1702             : 
    1703             :     // Asynchronously close the connection. We will null it below.
    1704           0 :     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
    1705             :   }
    1706             : 
    1707           0 :   CleanupDefaultDBConnection();
    1708             : 
    1709           0 :   mDefaultDBState = nullptr;
    1710             : }
    1711             : 
    1712             : // Null out the statements.
    1713             : // This must be done before closing the connection.
    1714             : void
    1715           0 : nsCookieService::CleanupCachedStatements()
    1716             : {
    1717           0 :   mDefaultDBState->stmtInsert = nullptr;
    1718           0 :   mDefaultDBState->stmtDelete = nullptr;
    1719           0 :   mDefaultDBState->stmtUpdate = nullptr;
    1720           0 : }
    1721             : 
    1722             : // Null out the listeners, and the database connection itself. This
    1723             : // will not null out the statements, cancel a pending read or
    1724             : // asynchronously close the connection -- these must be done
    1725             : // beforehand if necessary.
    1726             : void
    1727           0 : nsCookieService::CleanupDefaultDBConnection()
    1728             : {
    1729           0 :   MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
    1730           0 :   MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
    1731           0 :   MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
    1732             : 
    1733             :   // Null out the database connections. If 'dbConn' has not been used for any
    1734             :   // asynchronous operations yet, this will synchronously close it; otherwise,
    1735             :   // it's expected that the caller has performed an AsyncClose prior.
    1736           0 :   mDefaultDBState->dbConn = nullptr;
    1737           0 :   mDefaultDBState->syncConn = nullptr;
    1738             : 
    1739             :   // Manually null out our listeners. This is necessary because they hold a
    1740             :   // strong ref to the DBState itself. They'll stay alive until whatever
    1741             :   // statements are still executing complete.
    1742           0 :   mDefaultDBState->readListener = nullptr;
    1743           0 :   mDefaultDBState->insertListener = nullptr;
    1744           0 :   mDefaultDBState->updateListener = nullptr;
    1745           0 :   mDefaultDBState->removeListener = nullptr;
    1746           0 :   mDefaultDBState->closeListener = nullptr;
    1747           0 : }
    1748             : 
    1749             : void
    1750           0 : nsCookieService::HandleDBClosed(DBState* aDBState)
    1751             : {
    1752           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    1753             :     ("HandleDBClosed(): DBState %p closed", aDBState));
    1754             : 
    1755           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    1756             : 
    1757           0 :   switch (aDBState->corruptFlag) {
    1758             :   case DBState::OK: {
    1759             :     // Database is healthy. Notify of closure.
    1760           0 :     if (os) {
    1761           0 :       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
    1762             :     }
    1763           0 :     break;
    1764             :   }
    1765             :   case DBState::CLOSING_FOR_REBUILD: {
    1766             :     // Our close finished. Start the rebuild, and notify of db closure later.
    1767           0 :     RebuildCorruptDB(aDBState);
    1768           0 :     break;
    1769             :   }
    1770             :   case DBState::REBUILDING: {
    1771             :     // We encountered an error during rebuild, closed the database, and now
    1772             :     // here we are. We already have a 'cookies.sqlite.bak' from the original
    1773             :     // dead database; we don't want to overwrite it, so let's move this one to
    1774             :     // 'cookies.sqlite.bak-rebuild'.
    1775           0 :     nsCOMPtr<nsIFile> backupFile;
    1776           0 :     aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
    1777           0 :     nsresult rv = backupFile->MoveToNative(nullptr,
    1778           0 :       NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
    1779             : 
    1780           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
    1781             :       ("HandleDBClosed(): DBState %p encountered error rebuilding db; move to "
    1782             :        "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
    1783             :        aDBState, static_cast<uint32_t>(rv)));
    1784           0 :     if (os) {
    1785           0 :       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
    1786             :     }
    1787           0 :     break;
    1788             :   }
    1789             :   }
    1790           0 : }
    1791             : 
    1792             : void
    1793           0 : nsCookieService::HandleCorruptDB(DBState* aDBState)
    1794             : {
    1795           0 :   if (mDefaultDBState != aDBState) {
    1796             :     // We've either closed the state or we've switched profiles. It's getting
    1797             :     // a bit late to rebuild -- bail instead.
    1798           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
    1799             :       ("HandleCorruptDB(): DBState %p is already closed, aborting", aDBState));
    1800           0 :     return;
    1801             :   }
    1802             : 
    1803           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    1804             :     ("HandleCorruptDB(): DBState %p has corruptFlag %u", aDBState,
    1805             :       aDBState->corruptFlag));
    1806             : 
    1807             :   // Mark the database corrupt, so the close listener can begin reconstructing
    1808             :   // it.
    1809           0 :   switch (mDefaultDBState->corruptFlag) {
    1810             :   case DBState::OK: {
    1811             :     // Move to 'closing' state.
    1812           0 :     mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
    1813             : 
    1814             :     // Cancel any pending read and close the database. If we do have an
    1815             :     // in-flight read we want to throw away all the results so far -- we have no
    1816             :     // idea how consistent the database is. Note that we may have already
    1817             :     // canceled the read but not emptied our readSet; do so now.
    1818           0 :     mDefaultDBState->readSet.Clear();
    1819           0 :     if (mDefaultDBState->pendingRead) {
    1820           0 :       CancelAsyncRead(true);
    1821           0 :       mDefaultDBState->syncConn = nullptr;
    1822             :     }
    1823             : 
    1824           0 :     CleanupCachedStatements();
    1825           0 :     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
    1826           0 :     CleanupDefaultDBConnection();
    1827           0 :     break;
    1828             :   }
    1829             :   case DBState::CLOSING_FOR_REBUILD: {
    1830             :     // We had an error while waiting for close completion. That's OK, just
    1831             :     // ignore it -- we're rebuilding anyway.
    1832           0 :     return;
    1833             :   }
    1834             :   case DBState::REBUILDING: {
    1835             :     // We had an error while rebuilding the DB. Game over. Close the database
    1836             :     // and let the close handler do nothing; then we'll move it out of the way.
    1837           0 :     CleanupCachedStatements();
    1838           0 :     if (mDefaultDBState->dbConn) {
    1839           0 :       mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
    1840             :     }
    1841           0 :     CleanupDefaultDBConnection();
    1842           0 :     break;
    1843             :   }
    1844             :   }
    1845             : }
    1846             : 
    1847             : void
    1848           0 : nsCookieService::RebuildCorruptDB(DBState* aDBState)
    1849             : {
    1850           0 :   NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
    1851           0 :   NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
    1852             :     "should be in CLOSING_FOR_REBUILD state");
    1853             : 
    1854           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    1855             : 
    1856           0 :   aDBState->corruptFlag = DBState::REBUILDING;
    1857             : 
    1858           0 :   if (mDefaultDBState != aDBState) {
    1859             :     // We've either closed the state or we've switched profiles. It's getting
    1860             :     // a bit late to rebuild -- bail instead. In any case, we were waiting
    1861             :     // on rebuild completion to notify of the db closure, which won't happen --
    1862             :     // do so now.
    1863           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
    1864             :       ("RebuildCorruptDB(): DBState %p is stale, aborting", aDBState));
    1865           0 :     if (os) {
    1866           0 :       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
    1867             :     }
    1868           0 :     return;
    1869             :   }
    1870             : 
    1871           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    1872             :     ("RebuildCorruptDB(): creating new database"));
    1873             : 
    1874             :   // The database has been closed, and we're ready to rebuild. Open a
    1875             :   // connection.
    1876           0 :   OpenDBResult result = TryInitDB(true);
    1877           0 :   if (result != RESULT_OK) {
    1878             :     // We're done. Reset our DB connection and statements, and notify of
    1879             :     // closure.
    1880           0 :     COOKIE_LOGSTRING(LogLevel::Warning,
    1881             :       ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
    1882           0 :     CleanupCachedStatements();
    1883           0 :     CleanupDefaultDBConnection();
    1884           0 :     mDefaultDBState->corruptFlag = DBState::OK;
    1885           0 :     if (os) {
    1886           0 :       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
    1887             :     }
    1888           0 :     return;
    1889             :   }
    1890             : 
    1891             :   // Notify observers that we're beginning the rebuild.
    1892           0 :   if (os) {
    1893           0 :     os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
    1894             :   }
    1895             : 
    1896             :   // Enumerate the hash, and add cookies to the params array.
    1897           0 :   mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
    1898           0 :   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
    1899           0 :   stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
    1900           0 :   for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    1901           0 :     nsCookieEntry* entry = iter.Get();
    1902             : 
    1903           0 :     const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
    1904           0 :     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    1905           0 :       nsCookie* cookie = cookies[i];
    1906             : 
    1907           0 :       if (!cookie->IsSession()) {
    1908           0 :         bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
    1909             :       }
    1910             :     }
    1911             :   }
    1912             : 
    1913             :   // Make sure we've got something to write. If we don't, we're done.
    1914             :   uint32_t length;
    1915           0 :   paramsArray->GetLength(&length);
    1916           0 :   if (length == 0) {
    1917           0 :     COOKIE_LOGSTRING(LogLevel::Debug,
    1918             :       ("RebuildCorruptDB(): nothing to write, rebuild complete"));
    1919           0 :     mDefaultDBState->corruptFlag = DBState::OK;
    1920           0 :     return;
    1921             :   }
    1922             : 
    1923             :   // Execute the statement. If any errors crop up, we won't try again.
    1924           0 :   DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
    1925           0 :   NS_ASSERT_SUCCESS(rv);
    1926           0 :   nsCOMPtr<mozIStoragePendingStatement> handle;
    1927           0 :   rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
    1928           0 :   NS_ASSERT_SUCCESS(rv);
    1929             : }
    1930             : 
    1931           0 : nsCookieService::~nsCookieService()
    1932             : {
    1933           0 :   CloseDBStates();
    1934             : 
    1935           0 :   UnregisterWeakMemoryReporter(this);
    1936             : 
    1937           0 :   gCookieService = nullptr;
    1938           0 : }
    1939             : 
    1940             : NS_IMETHODIMP
    1941           0 : nsCookieService::Observe(nsISupports     *aSubject,
    1942             :                          const char      *aTopic,
    1943             :                          const char16_t *aData)
    1944             : {
    1945             :   // check the topic
    1946           0 :   if (!strcmp(aTopic, "profile-before-change")) {
    1947             :     // The profile is about to change,
    1948             :     // or is going away because the application is shutting down.
    1949             : 
    1950             :     // Close the default DB connection and null out our DBStates before
    1951             :     // changing.
    1952           0 :     CloseDBStates();
    1953             : 
    1954           0 :   } else if (!strcmp(aTopic, "profile-do-change")) {
    1955           0 :     NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
    1956           0 :     NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
    1957             : 
    1958             :     // the profile has already changed; init the db from the new location.
    1959             :     // if we are in the private browsing state, however, we do not want to read
    1960             :     // data into it - we should instead put it into the default state, so it's
    1961             :     // ready for us if and when we switch back to it.
    1962           0 :     InitDBStates();
    1963             : 
    1964           0 :   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
    1965           0 :     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
    1966           0 :     if (prefBranch)
    1967           0 :       PrefChanged(prefBranch);
    1968             : 
    1969           0 :   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
    1970             :     // Flush all the cookies stored by private browsing contexts
    1971           0 :     mPrivateDBState = new DBState();
    1972             :   }
    1973             : 
    1974             : 
    1975           0 :   return NS_OK;
    1976             : }
    1977             : 
    1978             : NS_IMETHODIMP
    1979           0 : nsCookieService::GetCookieString(nsIURI     *aHostURI,
    1980             :                                  nsIChannel *aChannel,
    1981             :                                  char       **aCookie)
    1982             : {
    1983           0 :   return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
    1984             : }
    1985             : 
    1986             : NS_IMETHODIMP
    1987           6 : nsCookieService::GetCookieStringFromHttp(nsIURI     *aHostURI,
    1988             :                                          nsIURI     *aFirstURI,
    1989             :                                          nsIChannel *aChannel,
    1990             :                                          char       **aCookie)
    1991             : {
    1992           6 :   return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
    1993             : }
    1994             : 
    1995             : nsresult
    1996           6 : nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
    1997             :                                        nsIChannel *aChannel,
    1998             :                                        bool aHttpBound,
    1999             :                                        char** aCookie)
    2000             : {
    2001           6 :   NS_ENSURE_ARG(aHostURI);
    2002           6 :   NS_ENSURE_ARG(aCookie);
    2003             : 
    2004             :   // Determine whether the request is foreign. Failure is acceptable.
    2005           6 :   bool isForeign = true;
    2006           6 :   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
    2007             : 
    2008             :   // Get originAttributes.
    2009          12 :   OriginAttributes attrs;
    2010           6 :   if (aChannel) {
    2011           6 :     NS_GetOriginAttributes(aChannel, attrs);
    2012             :   }
    2013             : 
    2014          12 :   nsAutoCString result;
    2015           6 :   GetCookieStringInternal(aHostURI, isForeign, aHttpBound, attrs, result);
    2016           6 :   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
    2017           6 :   return NS_OK;
    2018             : }
    2019             : 
    2020             : NS_IMETHODIMP
    2021           0 : nsCookieService::SetCookieString(nsIURI     *aHostURI,
    2022             :                                  nsIPrompt  *aPrompt,
    2023             :                                  const char *aCookieHeader,
    2024             :                                  nsIChannel *aChannel)
    2025             : {
    2026             :   // The aPrompt argument is deprecated and unused.  Avoid introducing new
    2027             :   // code that uses this argument by warning if the value is non-null.
    2028           0 :   MOZ_ASSERT(!aPrompt);
    2029           0 :   if (aPrompt) {
    2030             :     nsCOMPtr<nsIConsoleService> aConsoleService =
    2031           0 :         do_GetService("@mozilla.org/consoleservice;1");
    2032           0 :     if (aConsoleService) {
    2033           0 :       aConsoleService->LogStringMessage(
    2034           0 :         u"Non-null prompt ignored by nsCookieService.");
    2035             :     }
    2036             :   }
    2037             :   return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
    2038           0 :                                false);
    2039             : }
    2040             : 
    2041             : NS_IMETHODIMP
    2042           0 : nsCookieService::SetCookieStringFromHttp(nsIURI     *aHostURI,
    2043             :                                          nsIURI     *aFirstURI,
    2044             :                                          nsIPrompt  *aPrompt,
    2045             :                                          const char *aCookieHeader,
    2046             :                                          const char *aServerTime,
    2047             :                                          nsIChannel *aChannel)
    2048             : {
    2049             :   // The aPrompt argument is deprecated and unused.  Avoid introducing new
    2050             :   // code that uses this argument by warning if the value is non-null.
    2051           0 :   MOZ_ASSERT(!aPrompt);
    2052           0 :   if (aPrompt) {
    2053             :     nsCOMPtr<nsIConsoleService> aConsoleService =
    2054           0 :         do_GetService("@mozilla.org/consoleservice;1");
    2055           0 :     if (aConsoleService) {
    2056           0 :       aConsoleService->LogStringMessage(
    2057           0 :         u"Non-null prompt ignored by nsCookieService.");
    2058             :     }
    2059             :   }
    2060             :   return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
    2061           0 :                                true);
    2062             : }
    2063             : 
    2064             : nsresult
    2065           0 : nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
    2066             :                                        const char *aCookieHeader,
    2067             :                                        const char *aServerTime,
    2068             :                                        nsIChannel *aChannel,
    2069             :                                        bool aFromHttp)
    2070             : {
    2071           0 :   NS_ENSURE_ARG(aHostURI);
    2072           0 :   NS_ENSURE_ARG(aCookieHeader);
    2073             : 
    2074             :   // Determine whether the request is foreign. Failure is acceptable.
    2075           0 :   bool isForeign = true;
    2076           0 :   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
    2077             : 
    2078             :   // Get originAttributes.
    2079           0 :   OriginAttributes attrs;
    2080           0 :   if (aChannel) {
    2081           0 :     NS_GetOriginAttributes(aChannel, attrs);
    2082             :   }
    2083             : 
    2084           0 :   nsDependentCString cookieString(aCookieHeader);
    2085           0 :   nsDependentCString serverTime(aServerTime ? aServerTime : "");
    2086           0 :   SetCookieStringInternal(aHostURI, isForeign, cookieString,
    2087           0 :                           serverTime, aFromHttp, attrs, aChannel);
    2088           0 :   return NS_OK;
    2089             : }
    2090             : 
    2091             : void
    2092           0 : nsCookieService::SetCookieStringInternal(nsIURI                 *aHostURI,
    2093             :                                          bool                    aIsForeign,
    2094             :                                          nsDependentCString     &aCookieHeader,
    2095             :                                          const nsCString        &aServerTime,
    2096             :                                          bool                    aFromHttp,
    2097             :                                          const OriginAttributes &aOriginAttrs,
    2098             :                                          nsIChannel             *aChannel)
    2099             : {
    2100           0 :   NS_ASSERTION(aHostURI, "null host!");
    2101             : 
    2102           0 :   if (!mDBState) {
    2103           0 :     NS_WARNING("No DBState! Profile already closed?");
    2104           0 :     return;
    2105             :   }
    2106             : 
    2107           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    2108           0 :   mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    2109             : 
    2110             :   // get the base domain for the host URI.
    2111             :   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
    2112             :   // file:// URI's (i.e. with an empty host) are allowed, but any other
    2113             :   // scheme must have a non-empty host. A trailing dot in the host
    2114             :   // is acceptable.
    2115             :   bool requireHostMatch;
    2116           0 :   nsAutoCString baseDomain;
    2117           0 :   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
    2118           0 :   if (NS_FAILED(rv)) {
    2119           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    2120           0 :                       "couldn't get base domain from URI");
    2121           0 :     return;
    2122             :   }
    2123             : 
    2124           0 :   nsCookieKey key(baseDomain, aOriginAttrs);
    2125             : 
    2126             :   // check default prefs
    2127           0 :   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, aCookieHeader.get());
    2128             : 
    2129             :   // fire a notification if third party or if cookie was rejected
    2130             :   // (but not if there was an error)
    2131           0 :   switch (cookieStatus) {
    2132             :   case STATUS_REJECTED:
    2133           0 :     NotifyRejected(aHostURI);
    2134           0 :     if (aIsForeign) {
    2135           0 :       NotifyThirdParty(aHostURI, false, aChannel);
    2136             :     }
    2137           0 :     return; // Stop here
    2138             :   case STATUS_REJECTED_WITH_ERROR:
    2139           0 :     return;
    2140             :   case STATUS_ACCEPTED: // Fallthrough
    2141             :   case STATUS_ACCEPT_SESSION:
    2142           0 :     if (aIsForeign) {
    2143           0 :       NotifyThirdParty(aHostURI, true, aChannel);
    2144             :     }
    2145           0 :     break;
    2146             :   default:
    2147           0 :     break;
    2148             :   }
    2149             : 
    2150             :   // parse server local time. this is not just done here for efficiency
    2151             :   // reasons - if there's an error parsing it, and we need to default it
    2152             :   // to the current time, we must do it here since the current time in
    2153             :   // SetCookieInternal() will change for each cookie processed (e.g. if the
    2154             :   // user is prompted).
    2155             :   PRTime tempServerTime;
    2156             :   int64_t serverTime;
    2157           0 :   PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
    2158           0 :                                        &tempServerTime);
    2159           0 :   if (result == PR_SUCCESS) {
    2160           0 :     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
    2161             :   } else {
    2162           0 :     serverTime = PR_Now() / PR_USEC_PER_SEC;
    2163             :   }
    2164             : 
    2165             :   // process each cookie in the header
    2166           0 :   while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
    2167             :                            aCookieHeader, serverTime, aFromHttp, aChannel)) {
    2168             :     // document.cookie can only set one cookie at a time
    2169           0 :     if (!aFromHttp)
    2170           0 :       break;
    2171             :   }
    2172             : }
    2173             : 
    2174             : // notify observers that a cookie was rejected due to the users' prefs.
    2175             : void
    2176           0 : nsCookieService::NotifyRejected(nsIURI *aHostURI)
    2177             : {
    2178           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    2179           0 :   if (os) {
    2180           0 :     os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
    2181             :   }
    2182           0 : }
    2183             : 
    2184             : // notify observers that a third-party cookie was accepted/rejected
    2185             : // if the cookie issuer is unknown, it defaults to "?"
    2186             : void
    2187           0 : nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
    2188             : {
    2189           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    2190           0 :   if (!os) {
    2191           0 :     return;
    2192             :   }
    2193             : 
    2194             :   const char* topic;
    2195             : 
    2196           0 :   if (mDBState != mPrivateDBState) {
    2197             :     // Regular (non-private) browsing
    2198           0 :     if (aIsAccepted) {
    2199           0 :       topic = "third-party-cookie-accepted";
    2200             :     } else {
    2201           0 :       topic = "third-party-cookie-rejected";
    2202             :     }
    2203             :   } else {
    2204             :     // Private browsing
    2205           0 :     if (aIsAccepted) {
    2206           0 :       topic = "private-third-party-cookie-accepted";
    2207             :     } else {
    2208           0 :       topic = "private-third-party-cookie-rejected";
    2209             :     }
    2210             :   }
    2211             : 
    2212             :   do {
    2213             :     // Attempt to find the host of aChannel.
    2214           0 :     if (!aChannel) {
    2215           0 :       break;
    2216             :     }
    2217           0 :     nsCOMPtr<nsIURI> channelURI;
    2218           0 :     nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
    2219           0 :     if (NS_FAILED(rv)) {
    2220           0 :       break;
    2221             :     }
    2222             : 
    2223           0 :     nsAutoCString referringHost;
    2224           0 :     rv = channelURI->GetHost(referringHost);
    2225           0 :     if (NS_FAILED(rv)) {
    2226           0 :       break;
    2227             :     }
    2228             : 
    2229           0 :     nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
    2230           0 :     os->NotifyObservers(aHostURI, topic, referringHostUTF16.get());
    2231           0 :     return;
    2232             :   } while (false);
    2233             : 
    2234             :   // This can fail for a number of reasons, in which kind we fallback to "?"
    2235           0 :   os->NotifyObservers(aHostURI, topic, u"?");
    2236             : }
    2237             : 
    2238             : // notify observers that the cookie list changed. there are five possible
    2239             : // values for aData:
    2240             : // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
    2241             : // "added"   means a cookie was added. aSubject is the added cookie.
    2242             : // "changed" means a cookie was altered. aSubject is the new cookie.
    2243             : // "cleared" means the entire cookie list was cleared. aSubject is null.
    2244             : // "batch-deleted" means a set of cookies was purged. aSubject is the list of
    2245             : // cookies.
    2246             : void
    2247           0 : nsCookieService::NotifyChanged(nsISupports     *aSubject,
    2248             :                                const char16_t *aData,
    2249             :                                bool aOldCookieIsSession)
    2250             : {
    2251           0 :   const char* topic = mDBState == mPrivateDBState ?
    2252           0 :       "private-cookie-changed" : "cookie-changed";
    2253           0 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    2254           0 :   if (!os) {
    2255           0 :     return;
    2256             :   }
    2257             :   // Notify for topic "private-cookie-changed" or "cookie-changed"
    2258           0 :   os->NotifyObservers(aSubject, topic, aData);
    2259             : 
    2260             :   // Notify for topic "session-cookie-changed" to update the copy of session
    2261             :   // cookies in session restore component.
    2262             :   // Ignore private session cookies since they will not be restored.
    2263           0 :   if (mDBState == mPrivateDBState) {
    2264           0 :     return;
    2265             :   }
    2266             :   // Filter out notifications for individual non-session cookies.
    2267           0 :   if (NS_LITERAL_STRING("changed").Equals(aData) ||
    2268           0 :       NS_LITERAL_STRING("deleted").Equals(aData) ||
    2269           0 :       NS_LITERAL_STRING("added").Equals(aData)) {
    2270           0 :     nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
    2271           0 :     MOZ_ASSERT(xpcCookie);
    2272           0 :     auto cookie = static_cast<nsCookie*>(xpcCookie.get());
    2273           0 :     if (!cookie->IsSession() && !aOldCookieIsSession) {
    2274           0 :       return;
    2275             :     }
    2276             :   }
    2277           0 :   os->NotifyObservers(aSubject, "session-cookie-changed", aData);
    2278             : }
    2279             : 
    2280             : already_AddRefed<nsIArray>
    2281           0 : nsCookieService::CreatePurgeList(nsICookie2* aCookie)
    2282             : {
    2283             :   nsCOMPtr<nsIMutableArray> removedList =
    2284           0 :     do_CreateInstance(NS_ARRAY_CONTRACTID);
    2285           0 :   removedList->AppendElement(aCookie, false);
    2286           0 :   return removedList.forget();
    2287             : }
    2288             : 
    2289             : /******************************************************************************
    2290             :  * nsCookieService:
    2291             :  * public transaction helper impl
    2292             :  ******************************************************************************/
    2293             : 
    2294             : NS_IMETHODIMP
    2295           0 : nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback)
    2296             : {
    2297           0 :   NS_ENSURE_ARG(aCallback);
    2298           0 :   if (NS_WARN_IF(!mDefaultDBState->dbConn)) {
    2299           0 :     return NS_ERROR_NOT_AVAILABLE;
    2300             :   }
    2301           0 :   mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
    2302             : 
    2303           0 :   if (NS_FAILED(aCallback->Callback())) {
    2304           0 :     Unused << transaction.Rollback();
    2305           0 :     return NS_ERROR_FAILURE;
    2306             :   }
    2307           0 :   return NS_OK;
    2308             : }
    2309             : 
    2310             : /******************************************************************************
    2311             :  * nsCookieService:
    2312             :  * pref observer impl
    2313             :  ******************************************************************************/
    2314             : 
    2315             : void
    2316           1 : nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
    2317             : {
    2318             :   int32_t val;
    2319           1 :   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
    2320           1 :     mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
    2321             : 
    2322           1 :   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
    2323           0 :     mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
    2324             : 
    2325           1 :   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
    2326           0 :     mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
    2327             : 
    2328           1 :   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
    2329           0 :     mCookiePurgeAge =
    2330           0 :       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
    2331             :   }
    2332             : 
    2333             :   bool boolval;
    2334           1 :   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
    2335           1 :     mThirdPartySession = boolval;
    2336             : 
    2337           1 :   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
    2338           1 :     mLeaveSecureAlone = boolval;
    2339           1 : }
    2340             : 
    2341             : /******************************************************************************
    2342             :  * nsICookieManager impl:
    2343             :  * nsICookieManager
    2344             :  ******************************************************************************/
    2345             : 
    2346             : NS_IMETHODIMP
    2347           0 : nsCookieService::RemoveAll()
    2348             : {
    2349           0 :   if (!mDBState) {
    2350           0 :     NS_WARNING("No DBState! Profile already closed?");
    2351           0 :     return NS_ERROR_NOT_AVAILABLE;
    2352             :   }
    2353             : 
    2354           0 :   RemoveAllFromMemory();
    2355             : 
    2356             :   // clear the cookie file
    2357           0 :   if (mDBState->dbConn) {
    2358           0 :     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
    2359             : 
    2360             :     // Cancel any pending read. No further results will be received by our
    2361             :     // read listener.
    2362           0 :     if (mDefaultDBState->pendingRead) {
    2363           0 :       CancelAsyncRead(true);
    2364             :     }
    2365             : 
    2366           0 :     nsCOMPtr<mozIStorageAsyncStatement> stmt;
    2367           0 :     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    2368           0 :       "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
    2369           0 :     if (NS_SUCCEEDED(rv)) {
    2370           0 :       nsCOMPtr<mozIStoragePendingStatement> handle;
    2371           0 :       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
    2372           0 :         getter_AddRefs(handle));
    2373           0 :       NS_ASSERT_SUCCESS(rv);
    2374             :     } else {
    2375             :       // Recreate the database.
    2376           0 :       COOKIE_LOGSTRING(LogLevel::Debug,
    2377             :                        ("RemoveAll(): corruption detected with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
    2378           0 :       HandleCorruptDB(mDefaultDBState);
    2379             :     }
    2380             :   }
    2381             : 
    2382           0 :   NotifyChanged(nullptr, u"cleared");
    2383           0 :   return NS_OK;
    2384             : }
    2385             : 
    2386             : NS_IMETHODIMP
    2387           0 : nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
    2388             : {
    2389           0 :   if (!mDBState) {
    2390           0 :     NS_WARNING("No DBState! Profile already closed?");
    2391           0 :     return NS_ERROR_NOT_AVAILABLE;
    2392             :   }
    2393             : 
    2394           0 :   EnsureReadComplete();
    2395             : 
    2396           0 :   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
    2397           0 :   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    2398           0 :     const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
    2399           0 :     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    2400           0 :       cookieList.AppendObject(cookies[i]);
    2401             :     }
    2402             :   }
    2403             : 
    2404           0 :   return NS_NewArrayEnumerator(aEnumerator, cookieList);
    2405             : }
    2406             : 
    2407             : NS_IMETHODIMP
    2408           0 : nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator)
    2409             : {
    2410           0 :   if (!mDBState) {
    2411           0 :     NS_WARNING("No DBState! Profile already closed?");
    2412           0 :     return NS_ERROR_NOT_AVAILABLE;
    2413             :   }
    2414             : 
    2415           0 :   EnsureReadComplete();
    2416             : 
    2417           0 :   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
    2418           0 :   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    2419           0 :     const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
    2420           0 :     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    2421           0 :       nsCookie* cookie = cookies[i];
    2422             :       // Filter out non-session cookies.
    2423           0 :       if (cookie->IsSession()) {
    2424           0 :         cookieList.AppendObject(cookie);
    2425             :       }
    2426             :     }
    2427             :   }
    2428             : 
    2429           0 :   return NS_NewArrayEnumerator(aEnumerator, cookieList);
    2430             : }
    2431             : 
    2432             : static nsresult
    2433           0 : InitializeOriginAttributes(OriginAttributes* aAttrs,
    2434             :                            JS::HandleValue aOriginAttributes,
    2435             :                            JSContext* aCx,
    2436             :                            uint8_t aArgc,
    2437             :                            const char16_t* aAPI,
    2438             :                            const char16_t* aInterfaceSuffix)
    2439             : {
    2440           0 :   MOZ_ASSERT(aAttrs);
    2441           0 :   MOZ_ASSERT(aCx);
    2442           0 :   MOZ_ASSERT(aAPI);
    2443           0 :   MOZ_ASSERT(aInterfaceSuffix);
    2444             : 
    2445           0 :   if (aArgc == 0) {
    2446             :     const char16_t* params[] = {
    2447             :       aAPI,
    2448             :       aInterfaceSuffix
    2449           0 :     };
    2450             : 
    2451             :     // This is supposed to be temporary and in 1 or 2 releases we want to
    2452             :     // have originAttributes param as mandatory. But for now, we don't want to
    2453             :     // break existing addons, so we write a console message to inform the addon
    2454             :     // developers about it.
    2455           0 :     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
    2456           0 :                                     NS_LITERAL_CSTRING("Cookie Manager"),
    2457             :                                     nullptr,
    2458             :                                     nsContentUtils::eNECKO_PROPERTIES,
    2459             :                                     "nsICookieManagerAPIDeprecated",
    2460           0 :                                     params, ArrayLength(params));
    2461           0 :   } else if (aArgc == 1) {
    2462           0 :     if (!aOriginAttributes.isObject() ||
    2463           0 :         !aAttrs->Init(aCx, aOriginAttributes)) {
    2464           0 :       return NS_ERROR_INVALID_ARG;
    2465             :     }
    2466             :   }
    2467             : 
    2468           0 :   return NS_OK;
    2469             : }
    2470             : 
    2471             : NS_IMETHODIMP
    2472           0 : nsCookieService::Add(const nsACString &aHost,
    2473             :                      const nsACString &aPath,
    2474             :                      const nsACString &aName,
    2475             :                      const nsACString &aValue,
    2476             :                      bool              aIsSecure,
    2477             :                      bool              aIsHttpOnly,
    2478             :                      bool              aIsSession,
    2479             :                      int64_t           aExpiry,
    2480             :                      JS::HandleValue   aOriginAttributes,
    2481             :                      JSContext*        aCx,
    2482             :                      uint8_t           aArgc)
    2483             : {
    2484           0 :   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
    2485             : 
    2486           0 :   OriginAttributes attrs;
    2487           0 :   nsresult rv = InitializeOriginAttributes(&attrs,
    2488             :                                            aOriginAttributes,
    2489             :                                            aCx,
    2490             :                                            aArgc,
    2491             :                                            u"nsICookieManager2.add()",
    2492           0 :                                            u"2");
    2493           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2494             : 
    2495           0 :   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
    2496           0 :                    aIsSession, aExpiry, &attrs);
    2497             : }
    2498             : 
    2499             : NS_IMETHODIMP_(nsresult)
    2500           0 : nsCookieService::AddNative(const nsACString &aHost,
    2501             :                            const nsACString &aPath,
    2502             :                            const nsACString &aName,
    2503             :                            const nsACString &aValue,
    2504             :                            bool              aIsSecure,
    2505             :                            bool              aIsHttpOnly,
    2506             :                            bool              aIsSession,
    2507             :                            int64_t           aExpiry,
    2508             :                            OriginAttributes* aOriginAttributes)
    2509             : {
    2510           0 :   if (NS_WARN_IF(!aOriginAttributes)) {
    2511           0 :     return NS_ERROR_FAILURE;
    2512             :   }
    2513             : 
    2514           0 :   if (!mDBState) {
    2515           0 :     NS_WARNING("No DBState! Profile already closed?");
    2516           0 :     return NS_ERROR_NOT_AVAILABLE;
    2517             :   }
    2518             : 
    2519           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    2520           0 :   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    2521             : 
    2522             :   // first, normalize the hostname, and fail if it contains illegal characters.
    2523           0 :   nsAutoCString host(aHost);
    2524           0 :   nsresult rv = NormalizeHost(host);
    2525           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2526             : 
    2527             :   // get the base domain for the host URI.
    2528             :   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
    2529           0 :   nsAutoCString baseDomain;
    2530           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    2531           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2532             : 
    2533           0 :   int64_t currentTimeInUsec = PR_Now();
    2534           0 :   nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes);
    2535             : 
    2536             :   RefPtr<nsCookie> cookie =
    2537             :     nsCookie::Create(aName, aValue, host, aPath,
    2538             :                      aExpiry,
    2539             :                      currentTimeInUsec,
    2540             :                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
    2541             :                      aIsSession,
    2542             :                      aIsSecure,
    2543             :                      aIsHttpOnly,
    2544           0 :                      key.mOriginAttributes);
    2545           0 :   if (!cookie) {
    2546           0 :     return NS_ERROR_OUT_OF_MEMORY;
    2547             :   }
    2548             : 
    2549           0 :   AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
    2550           0 :   return NS_OK;
    2551             : }
    2552             : 
    2553             : 
    2554             : nsresult
    2555           0 : nsCookieService::Remove(const nsACString& aHost, const OriginAttributes& aAttrs,
    2556             :                         const nsACString& aName, const nsACString& aPath,
    2557             :                         bool aBlocked)
    2558             : {
    2559           0 :   if (!mDBState) {
    2560           0 :     NS_WARNING("No DBState! Profile already closed?");
    2561           0 :     return NS_ERROR_NOT_AVAILABLE;
    2562             :   }
    2563             : 
    2564           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    2565           0 :   mDBState = (aAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    2566             : 
    2567             :   // first, normalize the hostname, and fail if it contains illegal characters.
    2568           0 :   nsAutoCString host(aHost);
    2569           0 :   nsresult rv = NormalizeHost(host);
    2570           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2571             : 
    2572           0 :   nsAutoCString baseDomain;
    2573           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    2574           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2575             : 
    2576           0 :   nsListIter matchIter;
    2577           0 :   RefPtr<nsCookie> cookie;
    2578           0 :   if (FindCookie(nsCookieKey(baseDomain, aAttrs),
    2579             :                  host,
    2580           0 :                  PromiseFlatCString(aName),
    2581           0 :                  PromiseFlatCString(aPath),
    2582             :                  matchIter)) {
    2583           0 :     cookie = matchIter.Cookie();
    2584           0 :     RemoveCookieFromList(matchIter);
    2585             :   }
    2586             : 
    2587             :   // check if we need to add the host to the permissions blacklist.
    2588           0 :   if (aBlocked && mPermissionService) {
    2589             :     // strip off the domain dot, if necessary
    2590           0 :     if (!host.IsEmpty() && host.First() == '.')
    2591           0 :       host.Cut(0, 1);
    2592             : 
    2593           0 :     host.Insert(NS_LITERAL_CSTRING("http://"), 0);
    2594             : 
    2595           0 :     nsCOMPtr<nsIURI> uri;
    2596           0 :     NS_NewURI(getter_AddRefs(uri), host);
    2597             : 
    2598           0 :     if (uri)
    2599           0 :       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
    2600             :   }
    2601             : 
    2602           0 :   if (cookie) {
    2603             :     // Everything's done. Notify observers.
    2604           0 :     NotifyChanged(cookie, u"deleted");
    2605             :   }
    2606             : 
    2607           0 :   return NS_OK;
    2608             : }
    2609             : 
    2610             : NS_IMETHODIMP
    2611           0 : nsCookieService::Remove(const nsACString &aHost,
    2612             :                         const nsACString &aName,
    2613             :                         const nsACString &aPath,
    2614             :                         bool             aBlocked,
    2615             :                         JS::HandleValue  aOriginAttributes,
    2616             :                         JSContext*       aCx,
    2617             :                         uint8_t          aArgc)
    2618             : {
    2619           0 :   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
    2620             : 
    2621           0 :   OriginAttributes attrs;
    2622           0 :   nsresult rv = InitializeOriginAttributes(&attrs,
    2623             :                                            aOriginAttributes,
    2624             :                                            aCx,
    2625             :                                            aArgc,
    2626             :                                            u"nsICookieManager.remove()",
    2627           0 :                                            u"");
    2628           0 :   NS_ENSURE_SUCCESS(rv, rv);
    2629             : 
    2630           0 :   return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
    2631             : }
    2632             : 
    2633             : NS_IMETHODIMP_(nsresult)
    2634           0 : nsCookieService::RemoveNative(const nsACString &aHost,
    2635             :                               const nsACString &aName,
    2636             :                               const nsACString &aPath,
    2637             :                               bool aBlocked,
    2638             :                               OriginAttributes* aOriginAttributes)
    2639             : {
    2640           0 :   if (NS_WARN_IF(!aOriginAttributes)) {
    2641           0 :     return NS_ERROR_FAILURE;
    2642             :   }
    2643             : 
    2644           0 :   nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked);
    2645           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
    2646           0 :     return rv;
    2647             :   }
    2648             : 
    2649           0 :   return NS_OK;
    2650             : }
    2651             : 
    2652             : /******************************************************************************
    2653             :  * nsCookieService impl:
    2654             :  * private file I/O functions
    2655             :  ******************************************************************************/
    2656             : 
    2657             : // Begin an asynchronous read from the database.
    2658             : OpenDBResult
    2659           1 : nsCookieService::Read()
    2660             : {
    2661             :   // Set up a statement for the read. Note that our query specifies that
    2662             :   // 'baseDomain' not be nullptr -- see below for why.
    2663           2 :   nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
    2664           4 :   nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    2665             :     "SELECT "
    2666             :       "name, "
    2667             :       "value, "
    2668             :       "host, "
    2669             :       "path, "
    2670             :       "expiry, "
    2671             :       "lastAccessed, "
    2672             :       "creationTime, "
    2673             :       "isSecure, "
    2674             :       "isHttpOnly, "
    2675             :       "baseDomain, "
    2676             :       "originAttributes "
    2677             :     "FROM moz_cookies "
    2678           4 :     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
    2679           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    2680             : 
    2681             :   // Set up a statement to delete any rows with a nullptr 'baseDomain'
    2682             :   // column. This takes care of any cookies set by browsers that don't
    2683             :   // understand the 'baseDomain' column, where the database schema version
    2684             :   // is from one that does. (This would occur when downgrading.)
    2685           2 :   nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
    2686           4 :   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
    2687             :     "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
    2688           4 :     getter_AddRefs(stmtDeleteNull));
    2689           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    2690             : 
    2691             :   // Start a new connection for sync reads, to reduce contention with the
    2692             :   // background thread. We need to do this before we kick off write statements,
    2693             :   // since they can lock the database and prevent connections from being opened.
    2694           2 :   rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
    2695           2 :     getter_AddRefs(mDefaultDBState->syncConn));
    2696           1 :   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
    2697             : 
    2698             :   // Init our readSet hash and execute the statements. Note that, after this
    2699             :   // point, we cannot fail without altering the cleanup code in InitDBStates()
    2700             :   // to handle closing of the now-asynchronous connection.
    2701           1 :   mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
    2702             : 
    2703           2 :   mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
    2704           2 :   rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
    2705           2 :     getter_AddRefs(mDefaultDBState->pendingRead));
    2706           1 :   NS_ASSERT_SUCCESS(rv);
    2707             : 
    2708           2 :   nsCOMPtr<mozIStoragePendingStatement> handle;
    2709           2 :   rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
    2710           2 :     getter_AddRefs(handle));
    2711           1 :   NS_ASSERT_SUCCESS(rv);
    2712             : 
    2713           1 :   return RESULT_OK;
    2714             : }
    2715             : 
    2716             : // Extract data from a single result row and create an nsCookie.
    2717             : // This is templated since 'T' is different for sync vs async results.
    2718             : template<class T> nsCookie*
    2719           0 : nsCookieService::GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes)
    2720             : {
    2721             :   // Skip reading 'baseDomain' -- up to the caller.
    2722           0 :   nsCString name, value, host, path;
    2723           0 :   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
    2724           0 :   NS_ASSERT_SUCCESS(rv);
    2725           0 :   rv = aRow->GetUTF8String(IDX_VALUE, value);
    2726           0 :   NS_ASSERT_SUCCESS(rv);
    2727           0 :   rv = aRow->GetUTF8String(IDX_HOST, host);
    2728           0 :   NS_ASSERT_SUCCESS(rv);
    2729           0 :   rv = aRow->GetUTF8String(IDX_PATH, path);
    2730           0 :   NS_ASSERT_SUCCESS(rv);
    2731             : 
    2732           0 :   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
    2733           0 :   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
    2734           0 :   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
    2735           0 :   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
    2736           0 :   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
    2737             : 
    2738             :   // Create a new nsCookie and assign the data.
    2739           0 :   return nsCookie::Create(name, value, host, path,
    2740             :                           expiry,
    2741             :                           lastAccessed,
    2742             :                           creationTime,
    2743             :                           false,
    2744             :                           isSecure,
    2745             :                           isHttpOnly,
    2746           0 :                           aOriginAttributes);
    2747             : }
    2748             : 
    2749             : void
    2750           1 : nsCookieService::AsyncReadComplete()
    2751             : {
    2752             :   // We may be in the private browsing DB state, with a pending read on the
    2753             :   // default DB state. (This would occur if we started up in private browsing
    2754             :   // mode.) As long as we do all our operations on the default state, we're OK.
    2755           1 :   NS_ASSERTION(mDefaultDBState, "no default DBState");
    2756           1 :   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
    2757           1 :   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
    2758             : 
    2759           2 :   mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
    2760             :   // Merge the data read on the background thread with the data synchronously
    2761             :   // read on the main thread. Note that transactions on the cookie table may
    2762             :   // have occurred on the main thread since, making the background data stale.
    2763           1 :   for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
    2764           0 :     const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
    2765             : 
    2766             :     // Tiebreak: if the given base domain has already been read in, ignore
    2767             :     // the background data. Note that readSet may contain domains that were
    2768             :     // queried but found not to be in the db -- that's harmless.
    2769           0 :     if (mDefaultDBState->readSet.GetEntry(tuple.key))
    2770           0 :       continue;
    2771             : 
    2772           0 :     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
    2773             :   }
    2774           2 :   DebugOnly<nsresult> rv = transaction.Commit();
    2775           1 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    2776             : 
    2777           1 :   mDefaultDBState->stmtReadDomain = nullptr;
    2778           1 :   mDefaultDBState->pendingRead = nullptr;
    2779           1 :   mDefaultDBState->readListener = nullptr;
    2780             : 
    2781             :   // Close sync connection asynchronously: if we let destructor close, it may
    2782             :   // cause an expensive fsync operation on the main-thread.
    2783           1 :   mDefaultDBState->syncConn->AsyncClose(nullptr);
    2784           1 :   mDefaultDBState->syncConn = nullptr;
    2785           1 :   mDefaultDBState->hostArray.Clear();
    2786           1 :   mDefaultDBState->readSet.Clear();
    2787             : 
    2788           1 :   COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %" PRIu32 " cookies read",
    2789             :                                   mDefaultDBState->cookieCount));
    2790             : 
    2791           2 :   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    2792           1 :   if (os) {
    2793           1 :     os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
    2794             :   }
    2795           1 : }
    2796             : 
    2797             : void
    2798           0 : nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
    2799             : {
    2800             :   // We may be in the private browsing DB state, with a pending read on the
    2801             :   // default DB state. (This would occur if we started up in private browsing
    2802             :   // mode.) As long as we do all our operations on the default state, we're OK.
    2803           0 :   NS_ASSERTION(mDefaultDBState, "no default DBState");
    2804           0 :   NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
    2805           0 :   NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
    2806             : 
    2807             :   // Cancel the pending read, kill the read listener, and empty the array
    2808             :   // of data already read in on the background thread.
    2809           0 :   mDefaultDBState->readListener->Cancel();
    2810           0 :   DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
    2811           0 :   NS_ASSERT_SUCCESS(rv);
    2812             : 
    2813           0 :   mDefaultDBState->stmtReadDomain = nullptr;
    2814           0 :   mDefaultDBState->pendingRead = nullptr;
    2815           0 :   mDefaultDBState->readListener = nullptr;
    2816           0 :   mDefaultDBState->hostArray.Clear();
    2817             : 
    2818             :   // Only clear the 'readSet' table if we no longer need to know what set of
    2819             :   // data is already accounted for.
    2820           0 :   if (aPurgeReadSet)
    2821           0 :     mDefaultDBState->readSet.Clear();
    2822           0 : }
    2823             : 
    2824             : void
    2825           6 : nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
    2826             : {
    2827           6 :   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
    2828             :     "not in default db state");
    2829             : 
    2830             :   // Fast path 1: nothing to read, or we've already finished reading.
    2831           6 :   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
    2832          10 :     return;
    2833             : 
    2834             :   // Fast path 2: already read in this particular domain.
    2835           1 :   if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
    2836           0 :     return;
    2837             : 
    2838             :   // Read in the data synchronously.
    2839             :   // see IDX_NAME, etc. for parameter indexes
    2840             :   nsresult rv;
    2841           1 :   if (!mDefaultDBState->stmtReadDomain) {
    2842             :     // Cache the statement, since it's likely to be used again.
    2843           4 :     rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
    2844             :       "SELECT "
    2845             :         "name, "
    2846             :         "value, "
    2847             :         "host, "
    2848             :         "path, "
    2849             :         "expiry, "
    2850             :         "lastAccessed, "
    2851             :         "creationTime, "
    2852             :         "isSecure, "
    2853             :         "isHttpOnly "
    2854             :       "FROM moz_cookies "
    2855             :       "WHERE baseDomain = :baseDomain "
    2856             :       "  AND originAttributes = :originAttributes"),
    2857           4 :       getter_AddRefs(mDefaultDBState->stmtReadDomain));
    2858             : 
    2859           1 :     if (NS_FAILED(rv)) {
    2860             :       // Recreate the database.
    2861           0 :       COOKIE_LOGSTRING(LogLevel::Debug,
    2862             :         ("EnsureReadDomain(): corruption detected when creating statement "
    2863             :          "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
    2864           0 :       HandleCorruptDB(mDefaultDBState);
    2865           0 :       return;
    2866             :     }
    2867             :   }
    2868             : 
    2869           1 :   NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
    2870             : 
    2871           2 :   mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
    2872             : 
    2873           3 :   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
    2874           3 :     NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
    2875           1 :   NS_ASSERT_SUCCESS(rv);
    2876             : 
    2877           2 :   nsAutoCString suffix;
    2878           1 :   aKey.mOriginAttributes.CreateSuffix(suffix);
    2879           2 :   rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
    2880           2 :     NS_LITERAL_CSTRING("originAttributes"), suffix);
    2881           1 :   NS_ASSERT_SUCCESS(rv);
    2882             : 
    2883             :   bool hasResult;
    2884           2 :   nsCString name, value, host, path;
    2885           2 :   AutoTArray<RefPtr<nsCookie>, kMaxCookiesPerHost> array;
    2886             :   while (true) {
    2887           1 :     rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
    2888           1 :     if (NS_FAILED(rv)) {
    2889             :       // Recreate the database.
    2890           0 :       COOKIE_LOGSTRING(LogLevel::Debug,
    2891             :         ("EnsureReadDomain(): corruption detected when reading result "
    2892             :          "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
    2893           0 :       HandleCorruptDB(mDefaultDBState);
    2894           0 :       return;
    2895             :     }
    2896             : 
    2897           1 :     if (!hasResult)
    2898           1 :       break;
    2899             : 
    2900           0 :     array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain,
    2901           0 :                                          aKey.mOriginAttributes));
    2902             :   }
    2903             : 
    2904           2 :   mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
    2905             :   // Add the cookies to the table in a single operation. This makes sure that
    2906             :   // either all the cookies get added, or in the case of corruption, none.
    2907           1 :   for (uint32_t i = 0; i < array.Length(); ++i) {
    2908           0 :     AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
    2909             :   }
    2910           1 :   rv = transaction.Commit();
    2911           1 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    2912             : 
    2913             :   // Add it to the hashset of read entries, so we don't read it again.
    2914           1 :   mDefaultDBState->readSet.PutEntry(aKey);
    2915             : 
    2916           1 :   COOKIE_LOGSTRING(LogLevel::Debug,
    2917             :     ("EnsureReadDomain(): %" PRIuSIZE " cookies read for base domain %s, "
    2918             :      " originAttributes = %s", array.Length(), aKey.mBaseDomain.get(),
    2919             :      suffix.get()));
    2920             : }
    2921             : 
    2922             : void
    2923           0 : nsCookieService::EnsureReadComplete()
    2924             : {
    2925           0 :   NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
    2926             :     "not in default db state");
    2927             : 
    2928             :   // Fast path 1: nothing to read, or we've already finished reading.
    2929           0 :   if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
    2930           0 :     return;
    2931             : 
    2932             :   // Cancel the pending read, so we don't get any more results.
    2933           0 :   CancelAsyncRead(false);
    2934             : 
    2935             :   // Read in the data synchronously.
    2936             :   // see IDX_NAME, etc. for parameter indexes
    2937           0 :   nsCOMPtr<mozIStorageStatement> stmt;
    2938           0 :   nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
    2939             :     "SELECT "
    2940             :       "name, "
    2941             :       "value, "
    2942             :       "host, "
    2943             :       "path, "
    2944             :       "expiry, "
    2945             :       "lastAccessed, "
    2946             :       "creationTime, "
    2947             :       "isSecure, "
    2948             :       "isHttpOnly, "
    2949             :       "baseDomain, "
    2950             :       "originAttributes  "
    2951             :     "FROM moz_cookies "
    2952           0 :     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
    2953             : 
    2954           0 :   if (NS_FAILED(rv)) {
    2955             :     // Recreate the database.
    2956           0 :     COOKIE_LOGSTRING(LogLevel::Debug,
    2957             :       ("EnsureReadComplete(): corruption detected when creating statement "
    2958             :        "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
    2959           0 :     HandleCorruptDB(mDefaultDBState);
    2960           0 :     return;
    2961             :   }
    2962             : 
    2963           0 :   nsCString baseDomain, name, value, host, path;
    2964             :   bool hasResult;
    2965           0 :   nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
    2966             :   while (true) {
    2967           0 :     rv = stmt->ExecuteStep(&hasResult);
    2968           0 :     if (NS_FAILED(rv)) {
    2969             :       // Recreate the database.
    2970           0 :       COOKIE_LOGSTRING(LogLevel::Debug,
    2971             :         ("EnsureReadComplete(): corruption detected when reading result "
    2972             :          "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
    2973           0 :       HandleCorruptDB(mDefaultDBState);
    2974           0 :       return;
    2975             :     }
    2976             : 
    2977           0 :     if (!hasResult)
    2978           0 :       break;
    2979             : 
    2980             :     // Make sure we haven't already read the data.
    2981           0 :     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
    2982             : 
    2983           0 :     nsAutoCString suffix;
    2984           0 :     OriginAttributes attrs;
    2985           0 :     stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
    2986             :     // If PopulateFromSuffix failed we just ignore the OA attributes
    2987             :     // that we don't support
    2988           0 :     Unused << attrs.PopulateFromSuffix(suffix);
    2989             : 
    2990           0 :     nsCookieKey key(baseDomain, attrs);
    2991           0 :     if (mDefaultDBState->readSet.GetEntry(key))
    2992           0 :       continue;
    2993             : 
    2994           0 :     CookieDomainTuple* tuple = array.AppendElement();
    2995           0 :     tuple->key = key;
    2996           0 :     tuple->cookie = GetCookieFromRow(stmt, attrs);
    2997           0 :   }
    2998             : 
    2999           0 :   mozStorageTransaction transaction(mDefaultDBState->dbConn, false);
    3000             :   // Add the cookies to the table in a single operation. This makes sure that
    3001             :   // either all the cookies get added, or in the case of corruption, none.
    3002           0 :   for (uint32_t i = 0; i < array.Length(); ++i) {
    3003           0 :     CookieDomainTuple& tuple = array[i];
    3004           0 :     AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
    3005           0 :       false);
    3006             :   }
    3007           0 :   rv = transaction.Commit();
    3008           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    3009             : 
    3010           0 :   mDefaultDBState->syncConn = nullptr;
    3011           0 :   mDefaultDBState->readSet.Clear();
    3012             : 
    3013           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    3014             :     ("EnsureReadComplete(): %" PRIuSIZE " cookies read", array.Length()));
    3015             : }
    3016             : 
    3017             : NS_IMETHODIMP
    3018           0 : nsCookieService::ImportCookies(nsIFile *aCookieFile)
    3019             : {
    3020           0 :   if (!mDBState) {
    3021           0 :     NS_WARNING("No DBState! Profile already closed?");
    3022           0 :     return NS_ERROR_NOT_AVAILABLE;
    3023             :   }
    3024             : 
    3025             :   // Make sure we're in the default DB state. We don't want people importing
    3026             :   // cookies into a private browsing session!
    3027           0 :   if (mDBState != mDefaultDBState) {
    3028           0 :     NS_WARNING("Trying to import cookies in a private browsing session!");
    3029           0 :     return NS_ERROR_NOT_AVAILABLE;
    3030             :   }
    3031             : 
    3032             :   nsresult rv;
    3033           0 :   nsCOMPtr<nsIInputStream> fileInputStream;
    3034           0 :   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
    3035           0 :   if (NS_FAILED(rv)) return rv;
    3036             : 
    3037           0 :   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
    3038           0 :   if (NS_FAILED(rv)) return rv;
    3039             : 
    3040             :   // First, ensure we've read in everything from the database, if we have one.
    3041           0 :   EnsureReadComplete();
    3042             : 
    3043             :   static const char kTrue[] = "TRUE";
    3044             : 
    3045           0 :   nsAutoCString buffer, baseDomain;
    3046           0 :   bool isMore = true;
    3047             :   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
    3048             :   nsACString::char_iterator iter;
    3049             :   int32_t numInts;
    3050             :   int64_t expires;
    3051           0 :   bool isDomain, isHttpOnly = false;
    3052           0 :   uint32_t originalCookieCount = mDefaultDBState->cookieCount;
    3053             : 
    3054           0 :   int64_t currentTimeInUsec = PR_Now();
    3055           0 :   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
    3056             :   // we use lastAccessedCounter to keep cookies in recently-used order,
    3057             :   // so we start by initializing to currentTime (somewhat arbitrary)
    3058           0 :   int64_t lastAccessedCounter = currentTimeInUsec;
    3059             : 
    3060             :   /* file format is:
    3061             :    *
    3062             :    * host \t isDomain \t path \t secure \t expires \t name \t cookie
    3063             :    *
    3064             :    * if this format isn't respected we move onto the next line in the file.
    3065             :    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
    3066             :    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
    3067             :    * expires is a int64_t integer
    3068             :    * note 1: cookie can contain tabs.
    3069             :    * note 2: cookies will be stored in order of lastAccessed time:
    3070             :    *         most-recently used come first; least-recently-used come last.
    3071             :    */
    3072             : 
    3073             :   /*
    3074             :    * ...but due to bug 178933, we hide HttpOnly cookies from older code
    3075             :    * in a comment, so they don't expose HttpOnly cookies to JS.
    3076             :    *
    3077             :    * The format for HttpOnly cookies is
    3078             :    *
    3079             :    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
    3080             :    *
    3081             :    */
    3082             : 
    3083             :   // We will likely be adding a bunch of cookies to the DB, so we use async
    3084             :   // batching with storage to make this super fast.
    3085           0 :   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
    3086           0 :   if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
    3087           0 :     mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
    3088             :   }
    3089             : 
    3090           0 :   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
    3091           0 :     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) {
    3092           0 :       isHttpOnly = true;
    3093           0 :       hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1;
    3094           0 :     } else if (buffer.IsEmpty() || buffer.First() == '#') {
    3095           0 :       continue;
    3096             :     } else {
    3097           0 :       isHttpOnly = false;
    3098           0 :       hostIndex = 0;
    3099             :     }
    3100             : 
    3101             :     // this is a cheap, cheesy way of parsing a tab-delimited line into
    3102             :     // string indexes, which can be lopped off into substrings. just for
    3103             :     // purposes of obfuscation, it also checks that each token was found.
    3104             :     // todo: use iterators?
    3105           0 :     if ((isDomainIndex = buffer.FindChar('\t', hostIndex)     + 1) == 0 ||
    3106           0 :         (pathIndex     = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
    3107           0 :         (secureIndex   = buffer.FindChar('\t', pathIndex)     + 1) == 0 ||
    3108           0 :         (expiresIndex  = buffer.FindChar('\t', secureIndex)   + 1) == 0 ||
    3109           0 :         (nameIndex     = buffer.FindChar('\t', expiresIndex)  + 1) == 0 ||
    3110           0 :         (cookieIndex   = buffer.FindChar('\t', nameIndex)     + 1) == 0) {
    3111           0 :       continue;
    3112             :     }
    3113             : 
    3114             :     // check the expirytime first - if it's expired, ignore
    3115             :     // nullstomp the trailing tab, to avoid copying the string
    3116           0 :     buffer.BeginWriting(iter);
    3117           0 :     *(iter += nameIndex - 1) = char(0);
    3118           0 :     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
    3119           0 :     if (numInts != 1 || expires < currentTime) {
    3120           0 :       continue;
    3121             :     }
    3122             : 
    3123           0 :     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
    3124           0 :     const nsACString& host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
    3125             :     // check for bad legacy cookies (domain not starting with a dot, or containing a port),
    3126             :     // and discard
    3127           0 :     if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
    3128           0 :         host.Contains(':')) {
    3129           0 :       continue;
    3130             :     }
    3131             : 
    3132             :     // compute the baseDomain from the host
    3133           0 :     rv = GetBaseDomainFromHost(host, baseDomain);
    3134           0 :     if (NS_FAILED(rv))
    3135           0 :       continue;
    3136             : 
    3137             :     // pre-existing cookies have inIsolatedMozBrowser=false set by default
    3138             :     // constructor of OriginAttributes().
    3139           0 :     nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
    3140             : 
    3141             :     // Create a new nsCookie and assign the data. We don't know the cookie
    3142             :     // creation time, so just use the current time to generate a unique one.
    3143             :     RefPtr<nsCookie> newCookie =
    3144           0 :       nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
    3145           0 :                        Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
    3146             :                        host,
    3147           0 :                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
    3148             :                        expires,
    3149             :                        lastAccessedCounter,
    3150             :                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
    3151             :                        false,
    3152           0 :                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
    3153             :                        isHttpOnly,
    3154           0 :                        key.mOriginAttributes);
    3155           0 :     if (!newCookie) {
    3156           0 :       return NS_ERROR_OUT_OF_MEMORY;
    3157             :     }
    3158             : 
    3159             :     // trick: preserve the most-recently-used cookie ordering,
    3160             :     // by successively decrementing the lastAccessed time
    3161           0 :     lastAccessedCounter--;
    3162             : 
    3163           0 :     if (originalCookieCount == 0) {
    3164           0 :       AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
    3165             :     }
    3166             :     else {
    3167           0 :       AddInternal(key, newCookie, currentTimeInUsec,
    3168           0 :                   nullptr, nullptr, true);
    3169             :     }
    3170             :   }
    3171             : 
    3172             :   // If we need to write to disk, do so now.
    3173           0 :   if (paramsArray) {
    3174             :     uint32_t length;
    3175           0 :     paramsArray->GetLength(&length);
    3176           0 :     if (length) {
    3177           0 :       rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
    3178           0 :       NS_ASSERT_SUCCESS(rv);
    3179           0 :       nsCOMPtr<mozIStoragePendingStatement> handle;
    3180           0 :       rv = mDefaultDBState->stmtInsert->ExecuteAsync(
    3181           0 :         mDefaultDBState->insertListener, getter_AddRefs(handle));
    3182           0 :       NS_ASSERT_SUCCESS(rv);
    3183             :     }
    3184             :   }
    3185             : 
    3186             : 
    3187           0 :   COOKIE_LOGSTRING(LogLevel::Debug, ("ImportCookies(): %" PRIu32 " cookies imported",
    3188             :     mDefaultDBState->cookieCount));
    3189             : 
    3190           0 :   return NS_OK;
    3191             : }
    3192             : 
    3193             : /******************************************************************************
    3194             :  * nsCookieService impl:
    3195             :  * private GetCookie/SetCookie helpers
    3196             :  ******************************************************************************/
    3197             : 
    3198             : // helper function for GetCookieList
    3199           0 : static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
    3200             : 
    3201             : // Comparator class for sorting cookies before sending to a server.
    3202             : class CompareCookiesForSending
    3203             : {
    3204             : public:
    3205           0 :   bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
    3206             :   {
    3207           0 :     return aCookie1->CreationTime() == aCookie2->CreationTime() &&
    3208           0 :            aCookie2->Path().Length() == aCookie1->Path().Length();
    3209             :   }
    3210             : 
    3211           0 :   bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
    3212             :   {
    3213             :     // compare by cookie path length in accordance with RFC2109
    3214           0 :     int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
    3215           0 :     if (result != 0)
    3216           0 :       return result < 0;
    3217             : 
    3218             :     // when path lengths match, older cookies should be listed first.  this is
    3219             :     // required for backwards compatibility since some websites erroneously
    3220             :     // depend on receiving cookies in the order in which they were sent to the
    3221             :     // browser!  see bug 236772.
    3222           0 :     return aCookie1->CreationTime() < aCookie2->CreationTime();
    3223             :   }
    3224             : };
    3225             : 
    3226             : static bool
    3227           0 : DomainMatches(nsCookie* aCookie, const nsACString& aHost) {
    3228             :   // first, check for an exact host or domain cookie match, e.g. "google.com"
    3229             :   // or ".google.com"; second a subdomain match, e.g.
    3230             :   // host = "mail.google.com", cookie domain = ".google.com".
    3231           0 :   return aCookie->RawHost() == aHost ||
    3232           0 :       (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
    3233             : }
    3234             : 
    3235             : static bool
    3236           0 : PathMatches(nsCookie* aCookie, const nsACString& aPath) {
    3237             :   // calculate cookie path length, excluding trailing '/'
    3238           0 :   uint32_t cookiePathLen = aCookie->Path().Length();
    3239           0 :   if (cookiePathLen > 0 && aCookie->Path().Last() == '/')
    3240           0 :     --cookiePathLen;
    3241             : 
    3242             :   // if the given path is shorter than the cookie path, it doesn't match
    3243             :   // if the given path doesn't start with the cookie path, it doesn't match.
    3244           0 :   if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
    3245           0 :     return false;
    3246             : 
    3247             :   // if the given path is longer than the cookie path, and the first char after
    3248             :   // the cookie path is not a path delimiter, it doesn't match.
    3249           0 :   if (aPath.Length() > cookiePathLen &&
    3250           0 :       !ispathdelimiter(aPath.CharAt(cookiePathLen))) {
    3251             :     /*
    3252             :      * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
    3253             :      * '/' is the "standard" case; the '?' test allows a site at host/abc?def
    3254             :      * to receive a cookie that has a path attribute of abc.  this seems
    3255             :      * strange but at least one major site (citibank, bug 156725) depends
    3256             :      * on it.  The test for # and ; are put in to proactively avoid problems
    3257             :      * with other sites - these are the only other chars allowed in the path.
    3258             :      */
    3259           0 :     return false;
    3260             :   }
    3261             : 
    3262             :   // either the paths match exactly, or the cookie path is a prefix of
    3263             :   // the given path.
    3264           0 :   return true;
    3265             : }
    3266             : 
    3267             : void
    3268           6 : nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
    3269             :                                          bool aIsForeign,
    3270             :                                          bool aHttpBound,
    3271             :                                          const OriginAttributes& aOriginAttrs,
    3272             :                                          nsCString &aCookieString)
    3273             : {
    3274           6 :   NS_ASSERTION(aHostURI, "null host!");
    3275             : 
    3276           6 :   if (!mDBState) {
    3277           0 :     NS_WARNING("No DBState! Profile already closed?");
    3278           0 :     return;
    3279             :   }
    3280             : 
    3281           6 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    3282           6 :   mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    3283             : 
    3284             :   // get the base domain, host, and path from the URI.
    3285             :   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
    3286             :   // file:// URI's (i.e. with an empty host) are allowed, but any other
    3287             :   // scheme must have a non-empty host. A trailing dot in the host
    3288             :   // is acceptable.
    3289             :   bool requireHostMatch;
    3290           6 :   nsAutoCString baseDomain, hostFromURI, pathFromURI;
    3291           6 :   nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
    3292           6 :   if (NS_SUCCEEDED(rv))
    3293           6 :     rv = aHostURI->GetAsciiHost(hostFromURI);
    3294           6 :   if (NS_SUCCEEDED(rv))
    3295           6 :     rv = aHostURI->GetPath(pathFromURI);
    3296           6 :   if (NS_FAILED(rv)) {
    3297           0 :     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
    3298           0 :     return;
    3299             :   }
    3300             : 
    3301             :   // check default prefs
    3302           6 :   CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, nullptr);
    3303             : 
    3304             :   // for GetCookie(), we don't fire rejection notifications.
    3305           6 :   switch (cookieStatus) {
    3306             :   case STATUS_REJECTED:
    3307             :   case STATUS_REJECTED_WITH_ERROR:
    3308           0 :     return;
    3309             :   default:
    3310           6 :     break;
    3311             :   }
    3312             : 
    3313             :   // Note: The following permissions logic is mirrored in
    3314             :   // toolkit/modules/addons/MatchPattern.jsm:MatchPattern.matchesCookie().
    3315             :   // If it changes, please update that function, or file a bug for someone
    3316             :   // else to do so.
    3317             : 
    3318             :   // check if aHostURI is using an https secure protocol.
    3319             :   // if it isn't, then we can't send a secure cookie over the connection.
    3320             :   // if SchemeIs fails, assume an insecure connection, to be on the safe side
    3321             :   bool isSecure;
    3322           6 :   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
    3323           0 :     isSecure = false;
    3324             :   }
    3325             : 
    3326             :   nsCookie *cookie;
    3327           6 :   AutoTArray<nsCookie*, 8> foundCookieList;
    3328           6 :   int64_t currentTimeInUsec = PR_Now();
    3329           6 :   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
    3330           6 :   bool stale = false;
    3331             : 
    3332           6 :   nsCookieKey key(baseDomain, aOriginAttrs);
    3333           6 :   EnsureReadDomain(key);
    3334             : 
    3335             :   // perform the hash lookup
    3336           6 :   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
    3337           6 :   if (!entry)
    3338           6 :     return;
    3339             : 
    3340             :   // iterate the cookies!
    3341           0 :   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
    3342           0 :   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    3343           0 :     cookie = cookies[i];
    3344             : 
    3345             :     // check the host, since the base domain lookup is conservative.
    3346           0 :     if (!DomainMatches(cookie, hostFromURI))
    3347           0 :       continue;
    3348             : 
    3349             :     // if the cookie is secure and the host scheme isn't, we can't send it
    3350           0 :     if (cookie->IsSecure() && !isSecure)
    3351           0 :       continue;
    3352             : 
    3353             :     // if the cookie is httpOnly and it's not going directly to the HTTP
    3354             :     // connection, don't send it
    3355           0 :     if (cookie->IsHttpOnly() && !aHttpBound)
    3356           0 :       continue;
    3357             : 
    3358             :     // if the nsIURI path doesn't match the cookie path, don't send it back
    3359           0 :     if (!PathMatches(cookie, pathFromURI))
    3360           0 :       continue;
    3361             : 
    3362             :     // check if the cookie has expired
    3363           0 :     if (cookie->Expiry() <= currentTime) {
    3364           0 :       continue;
    3365             :     }
    3366             : 
    3367             :     // all checks passed - add to list and check if lastAccessed stamp needs updating
    3368           0 :     foundCookieList.AppendElement(cookie);
    3369           0 :     if (cookie->IsStale()) {
    3370           0 :       stale = true;
    3371             :     }
    3372             :   }
    3373             : 
    3374           0 :   int32_t count = foundCookieList.Length();
    3375           0 :   if (count == 0)
    3376           0 :     return;
    3377             : 
    3378             :   // update lastAccessed timestamps. we only do this if the timestamp is stale
    3379             :   // by a certain amount, to avoid thrashing the db during pageload.
    3380           0 :   if (stale) {
    3381             :     // Create an array of parameters to bind to our update statement. Batching
    3382             :     // is OK here since we're updating cookies with no interleaved operations.
    3383           0 :     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
    3384           0 :     mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
    3385           0 :     if (mDBState->dbConn) {
    3386           0 :       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
    3387             :     }
    3388             : 
    3389           0 :     for (int32_t i = 0; i < count; ++i) {
    3390           0 :       cookie = foundCookieList.ElementAt(i);
    3391             : 
    3392           0 :       if (cookie->IsStale()) {
    3393           0 :         UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
    3394             :       }
    3395             :     }
    3396             :     // Update the database now if necessary.
    3397           0 :     if (paramsArray) {
    3398             :       uint32_t length;
    3399           0 :       paramsArray->GetLength(&length);
    3400           0 :       if (length) {
    3401           0 :         DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
    3402           0 :         NS_ASSERT_SUCCESS(rv);
    3403           0 :         nsCOMPtr<mozIStoragePendingStatement> handle;
    3404           0 :         rv = stmt->ExecuteAsync(mDBState->updateListener,
    3405           0 :           getter_AddRefs(handle));
    3406           0 :         NS_ASSERT_SUCCESS(rv);
    3407             :       }
    3408             :     }
    3409             :   }
    3410             : 
    3411             :   // return cookies in order of path length; longest to shortest.
    3412             :   // this is required per RFC2109.  if cookies match in length,
    3413             :   // then sort by creation time (see bug 236772).
    3414           0 :   foundCookieList.Sort(CompareCookiesForSending());
    3415             : 
    3416           0 :   for (int32_t i = 0; i < count; ++i) {
    3417           0 :     cookie = foundCookieList.ElementAt(i);
    3418             : 
    3419             :     // check if we have anything to write
    3420           0 :     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
    3421             :       // if we've already added a cookie to the return list, append a "; " so
    3422             :       // that subsequent cookies are delimited in the final list.
    3423           0 :       if (!aCookieString.IsEmpty()) {
    3424           0 :         aCookieString.AppendLiteral("; ");
    3425             :       }
    3426             : 
    3427           0 :       if (!cookie->Name().IsEmpty()) {
    3428             :         // we have a name and value - write both
    3429           0 :         aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
    3430             :       } else {
    3431             :         // just write value
    3432           0 :         aCookieString += cookie->Value();
    3433             :       }
    3434             :     }
    3435             :   }
    3436             : 
    3437           0 :   if (!aCookieString.IsEmpty())
    3438           0 :     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
    3439             : }
    3440             : 
    3441             : // processes a single cookie, and returns true if there are more cookies
    3442             : // to be processed
    3443             : bool
    3444           0 : nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
    3445             :                                    const nsCookieKey             &aKey,
    3446             :                                    bool                           aRequireHostMatch,
    3447             :                                    CookieStatus                   aStatus,
    3448             :                                    nsDependentCString            &aCookieHeader,
    3449             :                                    int64_t                        aServerTime,
    3450             :                                    bool                           aFromHttp,
    3451             :                                    nsIChannel                    *aChannel)
    3452             : {
    3453           0 :   NS_ASSERTION(aHostURI, "null host!");
    3454             : 
    3455             :   // create a stack-based nsCookieAttributes, to store all the
    3456             :   // attributes parsed from the cookie
    3457           0 :   nsCookieAttributes cookieAttributes;
    3458             : 
    3459             :   // init expiryTime such that session cookies won't prematurely expire
    3460           0 :   cookieAttributes.expiryTime = INT64_MAX;
    3461             : 
    3462             :   // aCookieHeader is an in/out param to point to the next cookie, if
    3463             :   // there is one. Save the present value for logging purposes
    3464           0 :   nsDependentCString savedCookieHeader(aCookieHeader);
    3465             : 
    3466             :   // newCookie says whether there are multiple cookies in the header;
    3467             :   // so we can handle them separately.
    3468           0 :   bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
    3469             : 
    3470             :   // Collect telemetry on how often secure cookies are set from non-secure
    3471             :   // origins, and vice-versa.
    3472             :   //
    3473             :   // 0 = nonsecure and "http:"
    3474             :   // 1 = nonsecure and "https:"
    3475             :   // 2 = secure and "http:"
    3476             :   // 3 = secure and "https:"
    3477             :   bool isHTTPS;
    3478           0 :   nsresult rv = aHostURI->SchemeIs("https", &isHTTPS);
    3479           0 :   if (NS_SUCCEEDED(rv)) {
    3480           0 :     Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY,
    3481           0 :                           ((cookieAttributes.isSecure)? 0x02 : 0x00) |
    3482           0 :                           ((isHTTPS)? 0x01 : 0x00));
    3483             :   }
    3484             : 
    3485           0 :   int64_t currentTimeInUsec = PR_Now();
    3486             : 
    3487             :   // calculate expiry time of cookie.
    3488           0 :   cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
    3489             :                                          currentTimeInUsec / PR_USEC_PER_SEC);
    3490           0 :   if (aStatus == STATUS_ACCEPT_SESSION) {
    3491             :     // force lifetime to session. note that the expiration time, if set above,
    3492             :     // will still apply.
    3493           0 :     cookieAttributes.isSession = true;
    3494             :   }
    3495             : 
    3496             :   // reject cookie if it's over the size limit, per RFC2109
    3497           0 :   if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
    3498           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
    3499           0 :     return newCookie;
    3500             :   }
    3501             : 
    3502             :   const char illegalNameCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
    3503             :                                          0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
    3504             :                                          0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
    3505             :                                          0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
    3506             :                                          0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
    3507           0 :                                          0x1F, 0x00 };
    3508           0 :   if (cookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) {
    3509           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
    3510           0 :     return newCookie;
    3511             :   }
    3512             : 
    3513             :   // domain & path checks
    3514           0 :   if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
    3515           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
    3516           0 :     return newCookie;
    3517             :   }
    3518           0 :   if (!CheckPath(cookieAttributes, aHostURI)) {
    3519           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
    3520           0 :     return newCookie;
    3521             :   }
    3522             :   // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
    3523           0 :   if (!CheckPrefixes(cookieAttributes, isHTTPS)) {
    3524           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the prefix tests");
    3525           0 :     return newCookie;
    3526             :   }
    3527             : 
    3528             :   // reject cookie if value contains an RFC 6265 disallowed character - see
    3529             :   // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
    3530             :   // NOTE: this is not the full set of characters disallowed by 6265 - notably
    3531             :   // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is
    3532             :   // for parity with Chrome. This only applies to cookies set via the Set-Cookie
    3533             :   // header, as document.cookie is defined to be UTF-8. Hooray for
    3534             :   // symmetry!</sarcasm>
    3535             :   const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    3536             :                                      0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    3537             :                                      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
    3538             :                                      0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
    3539           0 :                                      0x1E, 0x1F, 0x3B, 0x00 };
    3540           0 :   if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
    3541           0 :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character");
    3542           0 :     return newCookie;
    3543             :   }
    3544             : 
    3545             :   // create a new nsCookie and copy attributes
    3546             :   RefPtr<nsCookie> cookie =
    3547             :     nsCookie::Create(cookieAttributes.name,
    3548             :                      cookieAttributes.value,
    3549             :                      cookieAttributes.host,
    3550             :                      cookieAttributes.path,
    3551             :                      cookieAttributes.expiryTime,
    3552             :                      currentTimeInUsec,
    3553             :                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
    3554           0 :                      cookieAttributes.isSession,
    3555           0 :                      cookieAttributes.isSecure,
    3556           0 :                      cookieAttributes.isHttpOnly,
    3557           0 :                      aKey.mOriginAttributes);
    3558           0 :   if (!cookie)
    3559           0 :     return newCookie;
    3560             : 
    3561             :   // check permissions from site permission list, or ask the user,
    3562             :   // to determine if we can set the cookie
    3563           0 :   if (mPermissionService) {
    3564             :     bool permission;
    3565           0 :     mPermissionService->CanSetCookie(aHostURI,
    3566             :                                      aChannel,
    3567           0 :                                      static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
    3568             :                                      &cookieAttributes.isSession,
    3569             :                                      &cookieAttributes.expiryTime,
    3570           0 :                                      &permission);
    3571           0 :     if (!permission) {
    3572           0 :       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
    3573           0 :       NotifyRejected(aHostURI);
    3574           0 :       return newCookie;
    3575             :     }
    3576             : 
    3577             :     // update isSession and expiry attributes, in case they changed
    3578           0 :     cookie->SetIsSession(cookieAttributes.isSession);
    3579           0 :     cookie->SetExpiry(cookieAttributes.expiryTime);
    3580             :   }
    3581             : 
    3582             :   // add the cookie to the list. AddInternal() takes care of logging.
    3583             :   // we get the current time again here, since it may have changed during prompting
    3584           0 :   AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
    3585           0 :               aFromHttp);
    3586           0 :   return newCookie;
    3587             : }
    3588             : 
    3589             : // this is a backend function for adding a cookie to the list, via SetCookie.
    3590             : // also used in the cookie manager, for profile migration from IE.
    3591             : // it either replaces an existing cookie; or adds the cookie to the hashtable,
    3592             : // and deletes a cookie (if maximum number of cookies has been
    3593             : // reached). also performs list maintenance by removing expired cookies.
    3594             : void
    3595           0 : nsCookieService::AddInternal(const nsCookieKey             &aKey,
    3596             :                              nsCookie                      *aCookie,
    3597             :                              int64_t                        aCurrentTimeInUsec,
    3598             :                              nsIURI                        *aHostURI,
    3599             :                              const char                    *aCookieHeader,
    3600             :                              bool                           aFromHttp)
    3601             : {
    3602           0 :   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
    3603             : 
    3604             :   // if the new cookie is httponly, make sure we're not coming from script
    3605           0 :   if (!aFromHttp && aCookie->IsHttpOnly()) {
    3606             :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3607           0 :       "cookie is httponly; coming from script");
    3608           0 :     return;
    3609             :   }
    3610             : 
    3611           0 :   bool isSecure = true;
    3612           0 :   if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
    3613           0 :     isSecure = false;
    3614             :   }
    3615             : 
    3616             :   // If the new cookie is non-https and wants to set secure flag,
    3617             :   // browser have to ignore this new cookie.
    3618             :   // (draft-ietf-httpbis-cookie-alone section 3.1)
    3619           0 :   if (mLeaveSecureAlone && aCookie->IsSecure() && !isSecure) {
    3620             :     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3621           0 :       "non-https cookie can't set secure flag");
    3622             :     Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3623           0 :                           BLOCKED_SECURE_SET_FROM_HTTP);
    3624           0 :     return;
    3625             :   }
    3626           0 :   nsListIter exactIter;
    3627           0 :   bool foundCookie = false;
    3628           0 :   foundCookie = FindCookie(aKey, aCookie->Host(),
    3629           0 :                            aCookie->Name(), aCookie->Path(), exactIter);
    3630           0 :   bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
    3631           0 :   bool oldCookieIsSession = false;
    3632           0 :   if (mLeaveSecureAlone) {
    3633             :     // Step1, call FindSecureCookie(). FindSecureCookie() would
    3634             :     // find the existing cookie with the security flag and has
    3635             :     // the same name, host and path of the new cookie, if there is any.
    3636             :     // Step2, Confirm new cookie's security setting. If any targeted
    3637             :     // cookie had been found in Step1, then confirm whether the
    3638             :     // new cookie could modify it. If the new created cookie’s
    3639             :     // "secure-only-flag" is not set, and the "scheme" component
    3640             :     // of the "request-uri" does not denote a "secure" protocol,
    3641             :     // then ignore the new cookie.
    3642             :     // (draft-ietf-httpbis-cookie-alone section 3.2)
    3643           0 :     if (!aCookie->IsSecure()
    3644           0 :          && (foundSecureExact || FindSecureCookie(aKey, aCookie))) {
    3645           0 :       if (!isSecure) {
    3646             :         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3647           0 :           "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
    3648           0 :         if (foundSecureExact) {
    3649             :           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3650           0 :                                 BLOCKED_DOWNGRADE_SECURE_EXACT);
    3651             :         } else {
    3652             :           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3653           0 :                                 BLOCKED_DOWNGRADE_SECURE_INEXACT);
    3654             :         }
    3655           0 :         return;
    3656             :       }
    3657             :       // A secure site is allowed to downgrade a secure cookie
    3658             :       // but we want to measure anyway.
    3659           0 :       if (foundSecureExact) {
    3660             :         Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3661           0 :                               DOWNGRADE_SECURE_FROM_SECURE_EXACT);
    3662             :       } else {
    3663             :         Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3664           0 :                               DOWNGRADE_SECURE_FROM_SECURE_INEXACT);
    3665             :       }
    3666             :     }
    3667             :   }
    3668             : 
    3669           0 :   RefPtr<nsCookie> oldCookie;
    3670           0 :   nsCOMPtr<nsIArray> purgedList;
    3671           0 :   if (foundCookie) {
    3672           0 :     oldCookie = exactIter.Cookie();
    3673           0 :     oldCookieIsSession = oldCookie->IsSession();
    3674             : 
    3675             :     // Check if the old cookie is stale (i.e. has already expired). If so, we
    3676             :     // need to be careful about the semantics of removing it and adding the new
    3677             :     // cookie: we want the behavior wrt adding the new cookie to be the same as
    3678             :     // if it didn't exist, but we still want to fire a removal notification.
    3679           0 :     if (oldCookie->Expiry() <= currentTime) {
    3680           0 :       if (aCookie->Expiry() <= currentTime) {
    3681             :         // The new cookie has expired and the old one is stale. Nothing to do.
    3682             :         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3683           0 :           "cookie has already expired");
    3684           0 :         return;
    3685             :       }
    3686             : 
    3687             :       // Remove the stale cookie. We save notification for later, once all list
    3688             :       // modifications are complete.
    3689           0 :       RemoveCookieFromList(exactIter);
    3690             :       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3691           0 :         "stale cookie was purged");
    3692           0 :       purgedList = CreatePurgeList(oldCookie);
    3693             : 
    3694             :       // We've done all we need to wrt removing and notifying the stale cookie.
    3695             :       // From here on out, we pretend pretend it didn't exist, so that we
    3696             :       // preserve expected notification semantics when adding the new cookie.
    3697           0 :       foundCookie = false;
    3698             : 
    3699             :     } else {
    3700             :       // If the old cookie is httponly, make sure we're not coming from script.
    3701           0 :       if (!aFromHttp && oldCookie->IsHttpOnly()) {
    3702             :         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3703           0 :           "previously stored cookie is httponly; coming from script");
    3704           0 :         return;
    3705             :       }
    3706             : 
    3707             :       // If the new cookie has the same value, expiry date, and isSecure,
    3708             :       // isSession, and isHttpOnly flags then we can just keep the old one.
    3709             :       // Only if any of these differ we would want to override the cookie.
    3710           0 :       if (oldCookie->Value().Equals(aCookie->Value()) &&
    3711           0 :           oldCookie->Expiry() == aCookie->Expiry() &&
    3712           0 :           oldCookie->IsSecure() == aCookie->IsSecure() &&
    3713           0 :           oldCookie->IsSession() == aCookie->IsSession() &&
    3714           0 :           oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
    3715             :           // We don't want to perform this optimization if the cookie is
    3716             :           // considered stale, since in this case we would need to update the
    3717             :           // database.
    3718           0 :           !oldCookie->IsStale()) {
    3719             :         // Update the last access time on the old cookie.
    3720           0 :         oldCookie->SetLastAccessed(aCookie->LastAccessed());
    3721           0 :         UpdateCookieOldestTime(mDBState, oldCookie);
    3722           0 :         return;
    3723             :       }
    3724             : 
    3725             :       // Remove the old cookie.
    3726           0 :       RemoveCookieFromList(exactIter);
    3727             : 
    3728             :       // If the new cookie has expired -- i.e. the intent was simply to delete
    3729             :       // the old cookie -- then we're done.
    3730           0 :       if (aCookie->Expiry() <= currentTime) {
    3731             :         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3732           0 :           "previously stored cookie was deleted");
    3733           0 :         NotifyChanged(oldCookie, u"deleted");
    3734           0 :         return;
    3735             :       }
    3736             : 
    3737             :       // Preserve creation time of cookie for ordering purposes.
    3738           0 :       aCookie->SetCreationTime(oldCookie->CreationTime());
    3739             :     }
    3740             : 
    3741             :   } else {
    3742             :     // check if cookie has already expired
    3743           0 :     if (aCookie->Expiry() <= currentTime) {
    3744             :       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
    3745           0 :         "cookie has already expired");
    3746           0 :       return;
    3747             :     }
    3748             : 
    3749             :     // check if we have to delete an old cookie.
    3750           0 :     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
    3751           0 :     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
    3752           0 :       nsListIter iter;
    3753             :       // Prioritize evicting insecure cookies.
    3754             :       // (draft-ietf-httpbis-cookie-alone section 3.3)
    3755           0 :       mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
    3756           0 :       int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
    3757           0 :       if (iter.entry == nullptr) {
    3758           0 :         if (aCookie->IsSecure()) {
    3759             :           // It's valid to evict a secure cookie for another secure cookie.
    3760           0 :           oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
    3761             :         } else {
    3762             :           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    3763           0 :                                 EVICTING_SECURE_BLOCKED);
    3764           0 :           COOKIE_LOGEVICTED(aCookie,
    3765             :             "Too many cookies for this domain and the new cookie is not a secure cookie");
    3766           0 :           return;
    3767             :         }
    3768             :       }
    3769             : 
    3770           0 :       MOZ_ASSERT(iter.entry);
    3771             : 
    3772           0 :       oldCookie = iter.Cookie();
    3773           0 :       if (oldestCookieTime > 0 && mLeaveSecureAlone) {
    3774           0 :         TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
    3775             :       }
    3776             : 
    3777             :       // remove the oldest cookie from the domain
    3778           0 :       RemoveCookieFromList(iter);
    3779           0 :       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
    3780           0 :       purgedList = CreatePurgeList(oldCookie);
    3781           0 :     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
    3782           0 :       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
    3783           0 :       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
    3784           0 :       if (maxAge >= purgeAge) {
    3785             :         // we're over both size and age limits by 10%; time to purge the table!
    3786             :         // do this by:
    3787             :         // 1) removing expired cookies;
    3788             :         // 2) evicting the balance of old cookies until we reach the size limit.
    3789             :         // note that the cookieOldestTime indicator can be pessimistic - if it's
    3790             :         // older than the actual oldest cookie, we'll just purge more eagerly.
    3791           0 :         purgedList = PurgeCookies(aCurrentTimeInUsec);
    3792             :       }
    3793             :     }
    3794             :   }
    3795             : 
    3796             :   // Add the cookie to the db. We do not supply a params array for batching
    3797             :   // because this might result in removals and additions being out of order.
    3798           0 :   AddCookieToList(aKey, aCookie, mDBState, nullptr);
    3799           0 :   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
    3800             : 
    3801             :   // Now that list mutations are complete, notify observers. We do it here
    3802             :   // because observers may themselves attempt to mutate the list.
    3803           0 :   if (purgedList) {
    3804           0 :     NotifyChanged(purgedList, u"batch-deleted");
    3805             :   }
    3806             : 
    3807           0 :   NotifyChanged(aCookie, foundCookie ? u"changed" : u"added", oldCookieIsSession);
    3808             : }
    3809             : 
    3810             : /******************************************************************************
    3811             :  * nsCookieService impl:
    3812             :  * private cookie header parsing functions
    3813             :  ******************************************************************************/
    3814             : 
    3815             : // The following comment block elucidates the function of ParseAttributes.
    3816             : /******************************************************************************
    3817             :  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
    3818             :  ** please note: this BNF deviates from both specifications, and reflects this
    3819             :  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
    3820             : 
    3821             :  ** Differences from RFC2109/2616 and explanations:
    3822             :     1. implied *LWS
    3823             :          The grammar described by this specification is word-based. Except
    3824             :          where noted otherwise, linear white space (<LWS>) can be included
    3825             :          between any two adjacent words (token or quoted-string), and
    3826             :          between adjacent words and separators, without changing the
    3827             :          interpretation of a field.
    3828             :        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
    3829             : 
    3830             :     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
    3831             :        common use inside values.
    3832             : 
    3833             :     3. tokens and values have looser restrictions on allowed characters than
    3834             :        spec. This is also due to certain characters being in common use inside
    3835             :        values. We allow only '=' to separate token/value pairs, and ';' to
    3836             :        terminate tokens or values. <LWS> is allowed within tokens and values
    3837             :        (see bug 206022).
    3838             : 
    3839             :     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
    3840             :        reject control chars or non-ASCII chars. This is erring on the loose
    3841             :        side, since there's probably no good reason to enforce this strictness.
    3842             : 
    3843             :     5. cookie <NAME> is optional, where spec requires it. This is a fairly
    3844             :        trivial case, but allows the flexibility of setting only a cookie <VALUE>
    3845             :        with a blank <NAME> and is required by some sites (see bug 169091).
    3846             : 
    3847             :     6. Attribute "HttpOnly", not covered in the RFCs, is supported
    3848             :        (see bug 178993).
    3849             : 
    3850             :  ** Begin BNF:
    3851             :     token         = 1*<any allowed-chars except separators>
    3852             :     value         = 1*<any allowed-chars except value-sep>
    3853             :     separators    = ";" | "="
    3854             :     value-sep     = ";"
    3855             :     cookie-sep    = CR | LF
    3856             :     allowed-chars = <any OCTET except NUL or cookie-sep>
    3857             :     OCTET         = <any 8-bit sequence of data>
    3858             :     LWS           = SP | HT
    3859             :     NUL           = <US-ASCII NUL, null control character (0)>
    3860             :     CR            = <US-ASCII CR, carriage return (13)>
    3861             :     LF            = <US-ASCII LF, linefeed (10)>
    3862             :     SP            = <US-ASCII SP, space (32)>
    3863             :     HT            = <US-ASCII HT, horizontal-tab (9)>
    3864             : 
    3865             :     set-cookie    = "Set-Cookie:" cookies
    3866             :     cookies       = cookie *( cookie-sep cookie )
    3867             :     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
    3868             :     NAME          = token                                ; cookie name
    3869             :     VALUE         = value                                ; cookie value
    3870             :     cookie-av     = token ["=" value]
    3871             : 
    3872             :     valid values for cookie-av (checked post-parsing) are:
    3873             :     cookie-av     = "Path"    "=" value
    3874             :                   | "Domain"  "=" value
    3875             :                   | "Expires" "=" value
    3876             :                   | "Max-Age" "=" value
    3877             :                   | "Comment" "=" value
    3878             :                   | "Version" "=" value
    3879             :                   | "Secure"
    3880             :                   | "HttpOnly"
    3881             : 
    3882             : ******************************************************************************/
    3883             : 
    3884             : // helper functions for GetTokenValue
    3885           0 : static inline bool iswhitespace     (char c) { return c == ' '  || c == '\t'; }
    3886           0 : static inline bool isterminator     (char c) { return c == '\n' || c == '\r'; }
    3887           0 : static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
    3888           0 : static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
    3889             : 
    3890             : // Parse a single token/value pair.
    3891             : // Returns true if a cookie terminator is found, so caller can parse new cookie.
    3892             : bool
    3893           0 : nsCookieService::GetTokenValue(nsACString::const_char_iterator &aIter,
    3894             :                                nsACString::const_char_iterator &aEndIter,
    3895             :                                nsDependentCSubstring                         &aTokenString,
    3896             :                                nsDependentCSubstring                         &aTokenValue,
    3897             :                                bool                                          &aEqualsFound)
    3898             : {
    3899             :   nsACString::const_char_iterator start, lastSpace;
    3900             :   // initialize value string to clear garbage
    3901           0 :   aTokenValue.Rebind(aIter, aIter);
    3902             : 
    3903             :   // find <token>, including any <LWS> between the end-of-token and the
    3904             :   // token separator. we'll remove trailing <LWS> next
    3905           0 :   while (aIter != aEndIter && iswhitespace(*aIter))
    3906           0 :     ++aIter;
    3907           0 :   start = aIter;
    3908           0 :   while (aIter != aEndIter && !istokenseparator(*aIter))
    3909           0 :     ++aIter;
    3910             : 
    3911             :   // remove trailing <LWS>; first check we're not at the beginning
    3912           0 :   lastSpace = aIter;
    3913           0 :   if (lastSpace != start) {
    3914           0 :     while (--lastSpace != start && iswhitespace(*lastSpace))
    3915           0 :       continue;
    3916           0 :     ++lastSpace;
    3917             :   }
    3918           0 :   aTokenString.Rebind(start, lastSpace);
    3919             : 
    3920           0 :   aEqualsFound = (*aIter == '=');
    3921           0 :   if (aEqualsFound) {
    3922             :     // find <value>
    3923           0 :     while (++aIter != aEndIter && iswhitespace(*aIter))
    3924           0 :       continue;
    3925             : 
    3926           0 :     start = aIter;
    3927             : 
    3928             :     // process <token>
    3929             :     // just look for ';' to terminate ('=' allowed)
    3930           0 :     while (aIter != aEndIter && !isvalueseparator(*aIter))
    3931           0 :       ++aIter;
    3932             : 
    3933             :     // remove trailing <LWS>; first check we're not at the beginning
    3934           0 :     if (aIter != start) {
    3935           0 :       lastSpace = aIter;
    3936           0 :       while (--lastSpace != start && iswhitespace(*lastSpace))
    3937           0 :         continue;
    3938           0 :       aTokenValue.Rebind(start, ++lastSpace);
    3939             :     }
    3940             :   }
    3941             : 
    3942             :   // aIter is on ';', or terminator, or EOS
    3943           0 :   if (aIter != aEndIter) {
    3944             :     // if on terminator, increment past & return true to process new cookie
    3945           0 :     if (isterminator(*aIter)) {
    3946           0 :       ++aIter;
    3947           0 :       return true;
    3948             :     }
    3949             :     // fall-through: aIter is on ';', increment and return false
    3950           0 :     ++aIter;
    3951             :   }
    3952           0 :   return false;
    3953             : }
    3954             : 
    3955             : // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
    3956             : // cookie struct here, because we don't know which one to use until we've parsed the header.
    3957             : bool
    3958           0 : nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
    3959             :                                  nsCookieAttributes &aCookieAttributes)
    3960             : {
    3961             :   static const char kPath[]    = "path";
    3962             :   static const char kDomain[]  = "domain";
    3963             :   static const char kExpires[] = "expires";
    3964             :   static const char kMaxage[]  = "max-age";
    3965             :   static const char kSecure[]  = "secure";
    3966             :   static const char kHttpOnly[]  = "httponly";
    3967             : 
    3968             :   nsACString::const_char_iterator tempBegin, tempEnd;
    3969             :   nsACString::const_char_iterator cookieStart, cookieEnd;
    3970           0 :   aCookieHeader.BeginReading(cookieStart);
    3971           0 :   aCookieHeader.EndReading(cookieEnd);
    3972             : 
    3973           0 :   aCookieAttributes.isSecure = false;
    3974           0 :   aCookieAttributes.isHttpOnly = false;
    3975             : 
    3976           0 :   nsDependentCSubstring tokenString(cookieStart, cookieStart);
    3977           0 :   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
    3978             :   bool newCookie, equalsFound;
    3979             : 
    3980             :   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
    3981             :   // if we find multiple cookies, return for processing
    3982             :   // note: if there's no '=', we assume token is <VALUE>. this is required by
    3983             :   //       some sites (see bug 169091).
    3984             :   // XXX fix the parser to parse according to <VALUE> grammar for this case
    3985           0 :   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
    3986           0 :   if (equalsFound) {
    3987           0 :     aCookieAttributes.name = tokenString;
    3988           0 :     aCookieAttributes.value = tokenValue;
    3989             :   } else {
    3990           0 :     aCookieAttributes.value = tokenString;
    3991             :   }
    3992             : 
    3993             :   // extract remaining attributes
    3994           0 :   while (cookieStart != cookieEnd && !newCookie) {
    3995           0 :     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
    3996             : 
    3997           0 :     if (!tokenValue.IsEmpty()) {
    3998           0 :       tokenValue.BeginReading(tempBegin);
    3999           0 :       tokenValue.EndReading(tempEnd);
    4000             :     }
    4001             : 
    4002             :     // decide which attribute we have, and copy the string
    4003           0 :     if (tokenString.LowerCaseEqualsLiteral(kPath))
    4004           0 :       aCookieAttributes.path = tokenValue;
    4005             : 
    4006           0 :     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
    4007           0 :       aCookieAttributes.host = tokenValue;
    4008             : 
    4009           0 :     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
    4010           0 :       aCookieAttributes.expires = tokenValue;
    4011             : 
    4012           0 :     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
    4013           0 :       aCookieAttributes.maxage = tokenValue;
    4014             : 
    4015             :     // ignore any tokenValue for isSecure; just set the boolean
    4016           0 :     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
    4017           0 :       aCookieAttributes.isSecure = true;
    4018             : 
    4019             :     // ignore any tokenValue for isHttpOnly (see bug 178993);
    4020             :     // just set the boolean
    4021           0 :     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
    4022           0 :       aCookieAttributes.isHttpOnly = true;
    4023             :   }
    4024             : 
    4025             :   // rebind aCookieHeader, in case we need to process another cookie
    4026           0 :   aCookieHeader.Rebind(cookieStart, cookieEnd);
    4027           0 :   return newCookie;
    4028             : }
    4029             : 
    4030             : /******************************************************************************
    4031             :  * nsCookieService impl:
    4032             :  * private domain & permission compliance enforcement functions
    4033             :  ******************************************************************************/
    4034             : 
    4035             : // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
    4036             : // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
    4037             : // dot may be present. If aHostURI is an IP address, an alias such as
    4038             : // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
    4039             : // be the exact host, and aRequireHostMatch will be true to indicate that
    4040             : // substring matches should not be performed.
    4041             : nsresult
    4042           6 : nsCookieService::GetBaseDomain(nsIURI    *aHostURI,
    4043             :                                nsCString &aBaseDomain,
    4044             :                                bool      &aRequireHostMatch)
    4045             : {
    4046             :   // get the base domain. this will fail if the host contains a leading dot,
    4047             :   // more than one trailing dot, or is otherwise malformed.
    4048           6 :   nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
    4049           6 :   aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
    4050             :                       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
    4051           6 :   if (aRequireHostMatch) {
    4052             :     // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
    4053             :     // such as 'co.uk', or the empty string. use the host as a key in such
    4054             :     // cases.
    4055           6 :     rv = aHostURI->GetAsciiHost(aBaseDomain);
    4056             :   }
    4057           6 :   NS_ENSURE_SUCCESS(rv, rv);
    4058             : 
    4059             :   // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
    4060           6 :   if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
    4061           0 :     return NS_ERROR_INVALID_ARG;
    4062             : 
    4063             :   // block any URIs without a host that aren't file:// URIs.
    4064           6 :   if (aBaseDomain.IsEmpty()) {
    4065           0 :     bool isFileURI = false;
    4066           0 :     aHostURI->SchemeIs("file", &isFileURI);
    4067           0 :     if (!isFileURI)
    4068           0 :       return NS_ERROR_INVALID_ARG;
    4069             :   }
    4070             : 
    4071           6 :   return NS_OK;
    4072             : }
    4073             : 
    4074             : // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
    4075             : // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
    4076             : // that aHost is already normalized, and it may contain a leading dot
    4077             : // (indicating that it represents a domain). A trailing dot may be present.
    4078             : // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
    4079             : // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
    4080             : // leading dot will be treated as an error.
    4081             : nsresult
    4082           0 : nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
    4083             :                                        nsCString        &aBaseDomain)
    4084             : {
    4085             :   // aHost must not be the string '.'.
    4086           0 :   if (aHost.Length() == 1 && aHost.Last() == '.')
    4087           0 :     return NS_ERROR_INVALID_ARG;
    4088             : 
    4089             :   // aHost may contain a leading dot; if so, strip it now.
    4090           0 :   bool domain = !aHost.IsEmpty() && aHost.First() == '.';
    4091             : 
    4092             :   // get the base domain. this will fail if the host contains a leading dot,
    4093             :   // more than one trailing dot, or is otherwise malformed.
    4094           0 :   nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
    4095           0 :   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
    4096             :       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
    4097             :     // aHost is either an IP address, an alias such as 'localhost', an eTLD
    4098             :     // such as 'co.uk', or the empty string. use the host as a key in such
    4099             :     // cases; however, we reject any such hosts with a leading dot, since it
    4100             :     // doesn't make sense for them to be domain cookies.
    4101           0 :     if (domain)
    4102           0 :       return NS_ERROR_INVALID_ARG;
    4103             : 
    4104           0 :     aBaseDomain = aHost;
    4105           0 :     return NS_OK;
    4106             :   }
    4107           0 :   return rv;
    4108             : }
    4109             : 
    4110             : // Normalizes the given hostname, component by component. ASCII/ACE
    4111             : // components are lower-cased, and UTF-8 components are normalized per
    4112             : // RFC 3454 and converted to ACE.
    4113             : nsresult
    4114           0 : nsCookieService::NormalizeHost(nsCString &aHost)
    4115             : {
    4116           0 :   if (!IsASCII(aHost)) {
    4117           0 :     nsAutoCString host;
    4118           0 :     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
    4119           0 :     if (NS_FAILED(rv))
    4120           0 :       return rv;
    4121             : 
    4122           0 :     aHost = host;
    4123             :   }
    4124             : 
    4125           0 :   ToLowerCase(aHost);
    4126           0 :   return NS_OK;
    4127             : }
    4128             : 
    4129             : // returns true if 'a' is equal to or a subdomain of 'b',
    4130             : // assuming no leading dots are present.
    4131           0 : static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
    4132             : {
    4133           0 :   if (a == b)
    4134           0 :     return true;
    4135           0 :   if (a.Length() > b.Length())
    4136           0 :     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
    4137           0 :   return false;
    4138             : }
    4139             : 
    4140             : CookieStatus
    4141           6 : nsCookieService::CheckPrefs(nsIURI          *aHostURI,
    4142             :                             bool             aIsForeign,
    4143             :                             const char      *aCookieHeader)
    4144             : {
    4145             :   nsresult rv;
    4146             : 
    4147             :   // don't let ftp sites get/set cookies (could be a security issue)
    4148             :   bool ftp;
    4149           6 :   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
    4150           0 :     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
    4151           0 :     return STATUS_REJECTED_WITH_ERROR;
    4152             :   }
    4153             : 
    4154             :   // check the permission list first; if we find an entry, it overrides
    4155             :   // default prefs. see bug 184059.
    4156           6 :   if (mPermissionService) {
    4157             :     nsCookieAccess access;
    4158             :     // Not passing an nsIChannel here is probably OK; our implementation
    4159             :     // doesn't do anything with it anyway.
    4160           6 :     rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
    4161             : 
    4162             :     // if we found an entry, use it
    4163           6 :     if (NS_SUCCEEDED(rv)) {
    4164           6 :       switch (access) {
    4165             :       case nsICookiePermission::ACCESS_DENY:
    4166           0 :         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
    4167           0 :                           aCookieHeader, "cookies are blocked for this site");
    4168           0 :         return STATUS_REJECTED;
    4169             : 
    4170             :       case nsICookiePermission::ACCESS_ALLOW:
    4171           0 :         return STATUS_ACCEPTED;
    4172             : 
    4173             :       case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
    4174           0 :         if (aIsForeign) {
    4175           0 :           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
    4176             :                             aCookieHeader, "third party cookies are blocked "
    4177           0 :                             "for this site");
    4178           0 :           return STATUS_REJECTED;
    4179             : 
    4180             :         }
    4181           0 :         return STATUS_ACCEPTED;
    4182             : 
    4183             :       case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
    4184           0 :         if (!aIsForeign)
    4185           0 :           return STATUS_ACCEPTED;
    4186           0 :         uint32_t priorCookieCount = 0;
    4187           0 :         nsAutoCString hostFromURI;
    4188           0 :         aHostURI->GetHost(hostFromURI);
    4189           0 :         CountCookiesFromHost(hostFromURI, &priorCookieCount);
    4190           0 :         if (priorCookieCount == 0) {
    4191           0 :           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
    4192             :                             aCookieHeader, "third party cookies are blocked "
    4193           0 :                             "for this site");
    4194           0 :           return STATUS_REJECTED;
    4195             :         }
    4196           0 :         return STATUS_ACCEPTED;
    4197             :       }
    4198             :     }
    4199             :   }
    4200             : 
    4201             :   // check default prefs
    4202           6 :   if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
    4203           0 :     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
    4204           0 :     return STATUS_REJECTED;
    4205             :   }
    4206             : 
    4207             :   // check if cookie is foreign
    4208           6 :   if (aIsForeign) {
    4209           2 :     if (mCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT && mThirdPartySession)
    4210           0 :       return STATUS_ACCEPT_SESSION;
    4211             : 
    4212           2 :     if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
    4213           0 :       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
    4214           0 :       return STATUS_REJECTED;
    4215             :     }
    4216             : 
    4217           2 :     if (mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
    4218           0 :       uint32_t priorCookieCount = 0;
    4219           0 :       nsAutoCString hostFromURI;
    4220           0 :       aHostURI->GetHost(hostFromURI);
    4221           0 :       CountCookiesFromHost(hostFromURI, &priorCookieCount);
    4222           0 :       if (priorCookieCount == 0) {
    4223           0 :         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
    4224           0 :         return STATUS_REJECTED;
    4225             :       }
    4226           0 :       if (mThirdPartySession)
    4227           0 :         return STATUS_ACCEPT_SESSION;
    4228             :     }
    4229             :   }
    4230             : 
    4231             :   // if nothing has complained, accept cookie
    4232           6 :   return STATUS_ACCEPTED;
    4233             : }
    4234             : 
    4235             : // processes domain attribute, and returns true if host has permission to set for this domain.
    4236             : bool
    4237           0 : nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
    4238             :                              nsIURI             *aHostURI,
    4239             :                              const nsCString    &aBaseDomain,
    4240             :                              bool                aRequireHostMatch)
    4241             : {
    4242             :   // Note: The logic in this function is mirrored in
    4243             :   // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
    4244             :   // If it changes, please update that function, or file a bug for someone
    4245             :   // else to do so.
    4246             : 
    4247             :   // get host from aHostURI
    4248           0 :   nsAutoCString hostFromURI;
    4249           0 :   aHostURI->GetAsciiHost(hostFromURI);
    4250             : 
    4251             :   // if a domain is given, check the host has permission
    4252           0 :   if (!aCookieAttributes.host.IsEmpty()) {
    4253             :     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
    4254           0 :     if (aCookieAttributes.host.Length() > 1 &&
    4255           0 :         aCookieAttributes.host.First() == '.') {
    4256           0 :       aCookieAttributes.host.Cut(0, 1);
    4257             :     }
    4258             : 
    4259             :     // switch to lowercase now, to avoid case-insensitive compares everywhere
    4260           0 :     ToLowerCase(aCookieAttributes.host);
    4261             : 
    4262             :     // check whether the host is either an IP address, an alias such as
    4263             :     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
    4264             :     // cases, require an exact string match for the domain, and leave the cookie
    4265             :     // as a non-domain one. bug 105917 originally noted the requirement to deal
    4266             :     // with IP addresses.
    4267           0 :     if (aRequireHostMatch)
    4268           0 :       return hostFromURI.Equals(aCookieAttributes.host);
    4269             : 
    4270             :     // ensure the proposed domain is derived from the base domain; and also
    4271             :     // that the host domain is derived from the proposed domain (per RFC2109).
    4272           0 :     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
    4273           0 :         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
    4274             :       // prepend a dot to indicate a domain cookie
    4275           0 :       aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
    4276           0 :       return true;
    4277             :     }
    4278             : 
    4279             :     /*
    4280             :      * note: RFC2109 section 4.3.2 requires that we check the following:
    4281             :      * that the portion of host not in domain does not contain a dot.
    4282             :      * this prevents hosts of the form x.y.co.nz from setting cookies in the
    4283             :      * entire .co.nz domain. however, it's only a only a partial solution and
    4284             :      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
    4285             :      */
    4286           0 :     return false;
    4287             :   }
    4288             : 
    4289             :   // no domain specified, use hostFromURI
    4290           0 :   aCookieAttributes.host = hostFromURI;
    4291           0 :   return true;
    4292             : }
    4293             : 
    4294             : nsCString
    4295           0 : GetPathFromURI(nsIURI* aHostURI)
    4296             : {
    4297             :   // strip down everything after the last slash to get the path,
    4298             :   // ignoring slashes in the query string part.
    4299             :   // if we can QI to nsIURL, that'll take care of the query string portion.
    4300             :   // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
    4301           0 :   nsAutoCString path;
    4302           0 :   nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
    4303           0 :   if (hostURL) {
    4304           0 :     hostURL->GetDirectory(path);
    4305             :   } else {
    4306           0 :     aHostURI->GetPath(path);
    4307           0 :     int32_t slash = path.RFindChar('/');
    4308           0 :     if (slash != kNotFound) {
    4309           0 :       path.Truncate(slash + 1);
    4310             :     }
    4311             :   }
    4312           0 :   return path;
    4313             : }
    4314             : 
    4315             : bool
    4316           0 : nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
    4317             :                            nsIURI             *aHostURI)
    4318             : {
    4319             :   // if a path is given, check the host has permission
    4320           0 :   if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
    4321           0 :     aCookieAttributes.path = GetPathFromURI(aHostURI);
    4322             : 
    4323             : #if 0
    4324             :   } else {
    4325             :     /**
    4326             :      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
    4327             :      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
    4328             :      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
    4329             :      * been disabled, unless we can evangelize these sites.
    4330             :      */
    4331             :     // get path from aHostURI
    4332             :     nsAutoCString pathFromURI;
    4333             :     if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
    4334             :         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
    4335             :       return false;
    4336             :     }
    4337             : #endif
    4338             :   }
    4339             : 
    4340           0 :   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
    4341           0 :       aCookieAttributes.path.Contains('\t'))
    4342           0 :     return false;
    4343             : 
    4344           0 :   return true;
    4345             : }
    4346             : 
    4347             : // CheckPrefixes
    4348             : //
    4349             : // Reject cookies whose name starts with the magic prefixes from
    4350             : // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
    4351             : // if they do not meet the criteria required by the prefix.
    4352             : //
    4353             : // Must not be called until after CheckDomain() and CheckPath() have
    4354             : // regularized and validated the nsCookieAttributes values!
    4355             : bool
    4356           0 : nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes,
    4357             :                                bool aSecureRequest)
    4358             : {
    4359             :   static const char kSecure[] = "__Secure-";
    4360             :   static const char kHost[]   = "__Host-";
    4361             :   static const int kSecureLen = sizeof( kSecure ) - 1;
    4362             :   static const int kHostLen   = sizeof( kHost ) - 1;
    4363             : 
    4364           0 :   bool isSecure = strncmp( aCookieAttributes.name.get(), kSecure, kSecureLen ) == 0;
    4365           0 :   bool isHost   = strncmp( aCookieAttributes.name.get(), kHost, kHostLen ) == 0;
    4366             : 
    4367           0 :   if ( !isSecure && !isHost ) {
    4368             :     // not one of the magic prefixes: carry on
    4369           0 :     return true;
    4370             :   }
    4371             : 
    4372           0 :   if ( !aSecureRequest || !aCookieAttributes.isSecure ) {
    4373             :     // the magic prefixes may only be used from a secure request and
    4374             :     // the secure attribute must be set on the cookie
    4375           0 :     return false;
    4376             :   }
    4377             : 
    4378           0 :   if ( isHost ) {
    4379             :     // The host prefix requires that the path is "/" and that the cookie
    4380             :     // had no domain attribute. CheckDomain() and CheckPath() MUST be run
    4381             :     // first to make sure invalid attributes are rejected and to regularlize
    4382             :     // them. In particular all explicit domain attributes result in a host
    4383             :     // that starts with a dot, and if the host doesn't start with a dot it
    4384             :     // correctly matches the true host.
    4385           0 :     if ( aCookieAttributes.host[0] == '.' ||
    4386           0 :          !aCookieAttributes.path.EqualsLiteral( "/" )) {
    4387           0 :       return false;
    4388             :     }
    4389             :   }
    4390             : 
    4391           0 :   return true;
    4392             : }
    4393             : 
    4394             : bool
    4395           0 : nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
    4396             :                            int64_t             aServerTime,
    4397             :                            int64_t             aCurrentTime)
    4398             : {
    4399             :   /* Determine when the cookie should expire. This is done by taking the difference between
    4400             :    * the server time and the time the server wants the cookie to expire, and adding that
    4401             :    * difference to the client time. This localizes the client time regardless of whether or
    4402             :    * not the TZ environment variable was set on the client.
    4403             :    *
    4404             :    * Note: We need to consider accounting for network lag here, per RFC.
    4405             :    */
    4406             :   // check for max-age attribute first; this overrides expires attribute
    4407           0 :   if (!aCookieAttributes.maxage.IsEmpty()) {
    4408             :     // obtain numeric value of maxageAttribute
    4409             :     int64_t maxage;
    4410           0 :     int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
    4411             : 
    4412             :     // default to session cookie if the conversion failed
    4413           0 :     if (numInts != 1) {
    4414           0 :       return true;
    4415             :     }
    4416             : 
    4417             :     // if this addition overflows, expiryTime will be less than currentTime
    4418             :     // and the cookie will be expired - that's okay.
    4419           0 :     aCookieAttributes.expiryTime = aCurrentTime + maxage;
    4420             : 
    4421             :   // check for expires attribute
    4422           0 :   } else if (!aCookieAttributes.expires.IsEmpty()) {
    4423             :     PRTime expires;
    4424             : 
    4425             :     // parse expiry time
    4426           0 :     if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
    4427           0 :       return true;
    4428             :     }
    4429             : 
    4430             :     // If set-cookie used absolute time to set expiration, and it can't use
    4431             :     // client time to set expiration.
    4432             :     // Because if current time be set in the future, but the cookie expire
    4433             :     // time be set less than current time and more than server time.
    4434             :     // The cookie item have to be used to the expired cookie.
    4435           0 :     aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC);
    4436             : 
    4437             :   // default to session cookie if no attributes found
    4438             :   } else {
    4439           0 :     return true;
    4440             :   }
    4441             : 
    4442           0 :   return false;
    4443             : }
    4444             : 
    4445             : /******************************************************************************
    4446             :  * nsCookieService impl:
    4447             :  * private cookielist management functions
    4448             :  ******************************************************************************/
    4449             : 
    4450             : void
    4451           0 : nsCookieService::RemoveAllFromMemory()
    4452             : {
    4453             :   // clearing the hashtable will call each nsCookieEntry's dtor,
    4454             :   // which releases all their respective children.
    4455           0 :   mDBState->hostTable.Clear();
    4456           0 :   mDBState->cookieCount = 0;
    4457           0 :   mDBState->cookieOldestTime = INT64_MAX;
    4458           0 : }
    4459             : 
    4460             : // comparator class for lastaccessed times of cookies.
    4461             : class CompareCookiesByAge {
    4462             : public:
    4463           0 :   bool Equals(const nsListIter &a, const nsListIter &b) const
    4464             :   {
    4465           0 :     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
    4466           0 :            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
    4467             :   }
    4468             : 
    4469           0 :   bool LessThan(const nsListIter &a, const nsListIter &b) const
    4470             :   {
    4471             :     // compare by lastAccessed time, and tiebreak by creationTime.
    4472           0 :     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
    4473           0 :     if (result != 0)
    4474           0 :       return result < 0;
    4475             : 
    4476           0 :     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
    4477             :   }
    4478             : };
    4479             : 
    4480             : // comparator class for sorting cookies by entry and index.
    4481             : class CompareCookiesByIndex {
    4482             : public:
    4483           0 :   bool Equals(const nsListIter &a, const nsListIter &b) const
    4484             :   {
    4485           0 :     NS_ASSERTION(a.entry != b.entry || a.index != b.index,
    4486             :       "cookie indexes should never be equal");
    4487           0 :     return false;
    4488             :   }
    4489             : 
    4490           0 :   bool LessThan(const nsListIter &a, const nsListIter &b) const
    4491             :   {
    4492             :     // compare by entryclass pointer, then by index.
    4493           0 :     if (a.entry != b.entry)
    4494           0 :       return a.entry < b.entry;
    4495             : 
    4496           0 :     return a.index < b.index;
    4497             :   }
    4498             : };
    4499             : 
    4500             : // purges expired and old cookies in a batch operation.
    4501             : already_AddRefed<nsIArray>
    4502           0 : nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
    4503             : {
    4504           0 :   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
    4505           0 :   EnsureReadComplete();
    4506             : 
    4507           0 :   uint32_t initialCookieCount = mDBState->cookieCount;
    4508           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    4509             :     ("PurgeCookies(): beginning purge with %" PRIu32 " cookies and %" PRId64 " oldest age",
    4510             :      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
    4511             : 
    4512             :   typedef nsTArray<nsListIter> PurgeList;
    4513           0 :   PurgeList purgeList(kMaxNumberOfCookies);
    4514             : 
    4515           0 :   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
    4516             : 
    4517             :   // Create a params array to batch the removals. This is OK here because
    4518             :   // all the removals are in order, and there are no interleaved additions.
    4519           0 :   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
    4520           0 :   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
    4521           0 :   if (mDBState->dbConn) {
    4522           0 :     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
    4523             :   }
    4524             : 
    4525           0 :   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
    4526           0 :   int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
    4527           0 :   int64_t oldestTime = INT64_MAX;
    4528             : 
    4529           0 :   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    4530           0 :     nsCookieEntry* entry = iter.Get();
    4531             : 
    4532           0 :     const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
    4533           0 :     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
    4534           0 :       nsListIter iter(entry, i);
    4535           0 :       nsCookie* cookie = cookies[i];
    4536             : 
    4537             :       // check if the cookie has expired
    4538           0 :       if (cookie->Expiry() <= currentTime) {
    4539           0 :         removedList->AppendElement(cookie, false);
    4540           0 :         COOKIE_LOGEVICTED(cookie, "Cookie expired");
    4541             : 
    4542             :         // remove from list; do not increment our iterator
    4543           0 :         gCookieService->RemoveCookieFromList(iter, paramsArray);
    4544             : 
    4545             :       } else {
    4546             :         // check if the cookie is over the age limit
    4547           0 :         if (cookie->LastAccessed() <= purgeTime) {
    4548           0 :           purgeList.AppendElement(iter);
    4549             : 
    4550           0 :         } else if (cookie->LastAccessed() < oldestTime) {
    4551             :           // reset our indicator
    4552           0 :           oldestTime = cookie->LastAccessed();
    4553             :         }
    4554             : 
    4555           0 :         ++i;
    4556             :       }
    4557             :     }
    4558             :   }
    4559             : 
    4560           0 :   uint32_t postExpiryCookieCount = mDBState->cookieCount;
    4561             : 
    4562             :   // now we have a list of iterators for cookies over the age limit.
    4563             :   // sort them by age, and then we'll see how many to remove...
    4564           0 :   purgeList.Sort(CompareCookiesByAge());
    4565             : 
    4566             :   // only remove old cookies until we reach the max cookie limit, no more.
    4567           0 :   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
    4568           0 :     mDBState->cookieCount - mMaxNumberOfCookies : 0;
    4569           0 :   if (purgeList.Length() > excess) {
    4570             :     // We're not purging everything in the list, so update our indicator.
    4571           0 :     oldestTime = purgeList[excess].Cookie()->LastAccessed();
    4572             : 
    4573           0 :     purgeList.SetLength(excess);
    4574             :   }
    4575             : 
    4576             :   // sort the list again, this time grouping cookies with a common entryclass
    4577             :   // together, and with ascending index. this allows us to iterate backwards
    4578             :   // over the list removing cookies, without having to adjust indexes as we go.
    4579           0 :   purgeList.Sort(CompareCookiesByIndex());
    4580           0 :   for (PurgeList::index_type i = purgeList.Length(); i--; ) {
    4581           0 :     nsCookie *cookie = purgeList[i].Cookie();
    4582           0 :     removedList->AppendElement(cookie, false);
    4583           0 :     COOKIE_LOGEVICTED(cookie, "Cookie too old");
    4584             : 
    4585           0 :     RemoveCookieFromList(purgeList[i], paramsArray);
    4586             :   }
    4587             : 
    4588             :   // Update the database if we have entries to purge.
    4589           0 :   if (paramsArray) {
    4590             :     uint32_t length;
    4591           0 :     paramsArray->GetLength(&length);
    4592           0 :     if (length) {
    4593           0 :       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
    4594           0 :       NS_ASSERT_SUCCESS(rv);
    4595           0 :       nsCOMPtr<mozIStoragePendingStatement> handle;
    4596           0 :       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
    4597           0 :       NS_ASSERT_SUCCESS(rv);
    4598             :     }
    4599             :   }
    4600             : 
    4601             :   // reset the oldest time indicator
    4602           0 :   mDBState->cookieOldestTime = oldestTime;
    4603             : 
    4604           0 :   COOKIE_LOGSTRING(LogLevel::Debug,
    4605             :     ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32 " purged; %" PRIu32
    4606             :      " remain; %" PRId64 " oldest age",
    4607             :      initialCookieCount - postExpiryCookieCount,
    4608             :      postExpiryCookieCount - mDBState->cookieCount,
    4609             :      mDBState->cookieCount,
    4610             :      aCurrentTimeInUsec - mDBState->cookieOldestTime));
    4611             : 
    4612           0 :   return removedList.forget();
    4613             : }
    4614             : 
    4615             : // find whether a given cookie has been previously set. this is provided by the
    4616             : // nsICookieManager2 interface.
    4617             : NS_IMETHODIMP
    4618           0 : nsCookieService::CookieExists(nsICookie2* aCookie,
    4619             :                               JS::HandleValue aOriginAttributes,
    4620             :                               JSContext* aCx,
    4621             :                               uint8_t aArgc,
    4622             :                               bool* aFoundCookie)
    4623             : {
    4624           0 :   NS_ENSURE_ARG_POINTER(aCookie);
    4625           0 :   NS_ENSURE_ARG_POINTER(aCx);
    4626           0 :   NS_ENSURE_ARG_POINTER(aFoundCookie);
    4627           0 :   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
    4628             : 
    4629           0 :   OriginAttributes attrs;
    4630           0 :   nsresult rv = InitializeOriginAttributes(&attrs,
    4631             :                                            aOriginAttributes,
    4632             :                                            aCx,
    4633             :                                            aArgc,
    4634             :                                            u"nsICookieManager2.cookieExists()",
    4635           0 :                                            u"2");
    4636           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4637             : 
    4638           0 :   return CookieExistsNative(aCookie, &attrs, aFoundCookie);
    4639             : }
    4640             : 
    4641             : NS_IMETHODIMP_(nsresult)
    4642           0 : nsCookieService::CookieExistsNative(nsICookie2* aCookie,
    4643             :                                     OriginAttributes* aOriginAttributes,
    4644             :                                     bool* aFoundCookie)
    4645             : {
    4646           0 :   NS_ENSURE_ARG_POINTER(aCookie);
    4647           0 :   NS_ENSURE_ARG_POINTER(aOriginAttributes);
    4648           0 :   NS_ENSURE_ARG_POINTER(aFoundCookie);
    4649             : 
    4650           0 :   if (!mDBState) {
    4651           0 :     NS_WARNING("No DBState! Profile already closed?");
    4652           0 :     return NS_ERROR_NOT_AVAILABLE;
    4653             :   }
    4654             : 
    4655           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    4656           0 :   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    4657             : 
    4658           0 :   nsAutoCString host, name, path;
    4659           0 :   nsresult rv = aCookie->GetHost(host);
    4660           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4661           0 :   rv = aCookie->GetName(name);
    4662           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4663           0 :   rv = aCookie->GetPath(path);
    4664           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4665             : 
    4666           0 :   nsAutoCString baseDomain;
    4667           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    4668           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4669             : 
    4670           0 :   nsListIter iter;
    4671           0 :   *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
    4672             :                              host, name, path, iter);
    4673           0 :   return NS_OK;
    4674             : }
    4675             : 
    4676             : // For a given base domain, find either an expired cookie or the oldest cookie
    4677             : // by lastAccessed time.
    4678             : int64_t
    4679           0 : nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
    4680             :                                  int64_t aCurrentTime,
    4681             :                                  nsIURI* aSource,
    4682             :                                  const mozilla::Maybe<bool> &aIsSecure,
    4683             :                                  nsListIter &aIter)
    4684             : {
    4685           0 :   aIter.entry = nullptr;
    4686           0 :   bool requireHostMatch = true;
    4687           0 :   nsAutoCString baseDomain, sourceHost, sourcePath;
    4688           0 :   if (aSource) {
    4689           0 :     GetBaseDomain(aSource, baseDomain, requireHostMatch);
    4690           0 :     aSource->GetAsciiHost(sourceHost);
    4691           0 :     sourcePath = GetPathFromURI(aSource);
    4692             :   }
    4693             : 
    4694           0 :   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
    4695             : 
    4696           0 :   int64_t oldestNonMatchingCookieTime = 0;
    4697           0 :   nsListIter oldestNonMatchingCookie;
    4698           0 :   oldestNonMatchingCookie.entry = nullptr;
    4699             : 
    4700           0 :   int64_t oldestCookieTime = 0;
    4701           0 :   nsListIter oldestCookie;
    4702           0 :   oldestCookie.entry = nullptr;
    4703             : 
    4704           0 :   int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
    4705           0 :   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    4706           0 :     nsCookie *cookie = cookies[i];
    4707             : 
    4708             :     // If we found an expired cookie, we're done.
    4709           0 :     if (cookie->Expiry() <= aCurrentTime) {
    4710           0 :       aIter.entry = aEntry;
    4711           0 :       aIter.index = i;
    4712           0 :       return -1;
    4713             :     }
    4714             : 
    4715           0 :     int64_t lastAccessed = cookie->LastAccessed();
    4716             :     // Record the age of the oldest cookie that is stored for this host.
    4717             :     // oldestCookieTime is the age of the oldest cookie with a matching
    4718             :     // secure flag, which may be more recent than an older cookie with
    4719             :     // a non-matching secure flag.
    4720           0 :     if (actualOldestCookieTime > lastAccessed) {
    4721           0 :       actualOldestCookieTime = lastAccessed;
    4722             :     }
    4723           0 :     if (aIsSecure.isSome() && !aIsSecure.value()) {
    4724             :       // We want to look for the oldest non-secure cookie first time through,
    4725             :       // then find the oldest secure cookie the second time we are called.
    4726           0 :       if (cookie->IsSecure()) {
    4727           0 :         continue;
    4728             :       }
    4729             :     }
    4730             : 
    4731             :     // This cookie is a candidate for eviction if we have no information about
    4732             :     // the source request, or if it is not a path or domain match against the
    4733             :     // source request.
    4734           0 :     bool isPrimaryEvictionCandidate = true;
    4735           0 :     if (aSource) {
    4736           0 :       isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
    4737             :     }
    4738             : 
    4739           0 :     if (isPrimaryEvictionCandidate &&
    4740           0 :         (!oldestNonMatchingCookie.entry ||
    4741             :          oldestNonMatchingCookieTime > lastAccessed)) {
    4742           0 :       oldestNonMatchingCookieTime = lastAccessed;
    4743           0 :       oldestNonMatchingCookie.entry = aEntry;
    4744           0 :       oldestNonMatchingCookie.index = i;
    4745             :     }
    4746             : 
    4747             :     // Check if we've found the oldest cookie so far.
    4748           0 :     if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
    4749           0 :       oldestCookieTime = lastAccessed;
    4750           0 :       oldestCookie.entry = aEntry;
    4751           0 :       oldestCookie.index = i;
    4752             :     }
    4753             :   }
    4754             : 
    4755             :   // Prefer to evict the oldest cookie with a non-matching path/domain,
    4756             :   // followed by the oldest matching cookie.
    4757           0 :   if (oldestNonMatchingCookie.entry) {
    4758           0 :     aIter = oldestNonMatchingCookie;
    4759             :   } else {
    4760           0 :     aIter = oldestCookie;
    4761             :   }
    4762             : 
    4763           0 :   return actualOldestCookieTime;
    4764             : }
    4765             : 
    4766             : void
    4767           0 : nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted,
    4768             :                                                  int64_t oldestCookieTime)
    4769             : {
    4770             :   // We need to record the evicting cookie to telemetry.
    4771           0 :   if (!aEvicted->IsSecure()) {
    4772           0 :     if (aEvicted->LastAccessed() > oldestCookieTime) {
    4773             :       Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    4774           0 :                             EVICTED_NEWER_INSECURE);
    4775             :     } else {
    4776             :       Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    4777           0 :                             EVICTED_OLDEST_COOKIE);
    4778             :     }
    4779             :   } else {
    4780             :     Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
    4781           0 :                           EVICTED_PREFERRED_COOKIE);
    4782             :   }
    4783           0 : }
    4784             : 
    4785             : // count the number of cookies stored by a particular host. this is provided by the
    4786             : // nsICookieManager2 interface.
    4787             : NS_IMETHODIMP
    4788           0 : nsCookieService::CountCookiesFromHost(const nsACString &aHost,
    4789             :                                       uint32_t         *aCountFromHost)
    4790             : {
    4791           0 :   if (!mDBState) {
    4792           0 :     NS_WARNING("No DBState! Profile already closed?");
    4793           0 :     return NS_ERROR_NOT_AVAILABLE;
    4794             :   }
    4795             : 
    4796             :   // first, normalize the hostname, and fail if it contains illegal characters.
    4797           0 :   nsAutoCString host(aHost);
    4798           0 :   nsresult rv = NormalizeHost(host);
    4799           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4800             : 
    4801           0 :   nsAutoCString baseDomain;
    4802           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    4803           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4804             : 
    4805           0 :   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
    4806           0 :   EnsureReadDomain(key);
    4807             : 
    4808             :   // Return a count of all cookies, including expired.
    4809           0 :   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
    4810           0 :   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
    4811           0 :   return NS_OK;
    4812             : }
    4813             : 
    4814             : // get an enumerator of cookies stored by a particular host. this is provided by the
    4815             : // nsICookieManager2 interface.
    4816             : NS_IMETHODIMP
    4817           0 : nsCookieService::GetCookiesFromHost(const nsACString     &aHost,
    4818             :                                     JS::HandleValue       aOriginAttributes,
    4819             :                                     JSContext*            aCx,
    4820             :                                     uint8_t               aArgc,
    4821             :                                     nsISimpleEnumerator **aEnumerator)
    4822             : {
    4823           0 :   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
    4824             : 
    4825           0 :   if (!mDBState) {
    4826           0 :     NS_WARNING("No DBState! Profile already closed?");
    4827           0 :     return NS_ERROR_NOT_AVAILABLE;
    4828             :   }
    4829             : 
    4830             :   // first, normalize the hostname, and fail if it contains illegal characters.
    4831           0 :   nsAutoCString host(aHost);
    4832           0 :   nsresult rv = NormalizeHost(host);
    4833           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4834             : 
    4835           0 :   nsAutoCString baseDomain;
    4836           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    4837           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4838             : 
    4839           0 :   OriginAttributes attrs;
    4840           0 :   rv = InitializeOriginAttributes(&attrs,
    4841             :                                   aOriginAttributes,
    4842             :                                   aCx,
    4843             :                                   aArgc,
    4844             :                                   u"nsICookieManager2.getCookiesFromHost()",
    4845           0 :                                   u"2");
    4846           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4847             : 
    4848           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    4849           0 :   mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
    4850             : 
    4851           0 :   nsCookieKey key = nsCookieKey(baseDomain, attrs);
    4852           0 :   EnsureReadDomain(key);
    4853             : 
    4854           0 :   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
    4855           0 :   if (!entry)
    4856           0 :     return NS_NewEmptyEnumerator(aEnumerator);
    4857             : 
    4858           0 :   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
    4859           0 :   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
    4860           0 :   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    4861           0 :     cookieList.AppendObject(cookies[i]);
    4862             :   }
    4863             : 
    4864           0 :   return NS_NewArrayEnumerator(aEnumerator, cookieList);
    4865             : }
    4866             : 
    4867             : NS_IMETHODIMP
    4868           0 : nsCookieService::GetCookiesWithOriginAttributes(const nsAString&    aPattern,
    4869             :                                                 const nsACString&   aHost,
    4870             :                                                 nsISimpleEnumerator **aEnumerator)
    4871             : {
    4872           0 :   mozilla::OriginAttributesPattern pattern;
    4873           0 :   if (!pattern.Init(aPattern)) {
    4874           0 :     return NS_ERROR_INVALID_ARG;
    4875             :   }
    4876             : 
    4877           0 :   nsAutoCString host(aHost);
    4878           0 :   nsresult rv = NormalizeHost(host);
    4879           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4880             : 
    4881           0 :   nsAutoCString baseDomain;
    4882           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    4883           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4884             : 
    4885           0 :   return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator);
    4886             : }
    4887             : 
    4888             : nsresult
    4889           0 : nsCookieService::GetCookiesWithOriginAttributes(
    4890             :     const mozilla::OriginAttributesPattern& aPattern,
    4891             :     const nsCString& aBaseDomain,
    4892             :     nsISimpleEnumerator **aEnumerator)
    4893             : {
    4894           0 :   if (!mDBState) {
    4895           0 :     NS_WARNING("No DBState! Profile already closed?");
    4896           0 :     return NS_ERROR_NOT_AVAILABLE;
    4897             :   }
    4898             : 
    4899           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    4900           0 :   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
    4901           0 :       aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
    4902             : 
    4903           0 :   nsCOMArray<nsICookie> cookies;
    4904           0 :   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    4905           0 :     nsCookieEntry* entry = iter.Get();
    4906             : 
    4907           0 :     if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
    4908           0 :       continue;
    4909             :     }
    4910             : 
    4911           0 :     if (!aPattern.Matches(entry->mOriginAttributes)) {
    4912           0 :       continue;
    4913             :     }
    4914             : 
    4915           0 :     const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies();
    4916             : 
    4917           0 :     for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
    4918           0 :       cookies.AppendObject(entryCookies[i]);
    4919             :     }
    4920             :   }
    4921             : 
    4922           0 :   return NS_NewArrayEnumerator(aEnumerator, cookies);
    4923             : }
    4924             : 
    4925             : NS_IMETHODIMP
    4926           0 : nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
    4927             :                                                    const nsACString& aHost)
    4928             : {
    4929           0 :   MOZ_ASSERT(XRE_IsParentProcess());
    4930             : 
    4931           0 :   mozilla::OriginAttributesPattern pattern;
    4932           0 :   if (!pattern.Init(aPattern)) {
    4933           0 :     return NS_ERROR_INVALID_ARG;
    4934             :   }
    4935             : 
    4936           0 :   nsAutoCString host(aHost);
    4937           0 :   nsresult rv = NormalizeHost(host);
    4938           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4939             : 
    4940           0 :   nsAutoCString baseDomain;
    4941           0 :   rv = GetBaseDomainFromHost(host, baseDomain);
    4942           0 :   NS_ENSURE_SUCCESS(rv, rv);
    4943             : 
    4944           0 :   return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
    4945             : }
    4946             : 
    4947             : nsresult
    4948           0 : nsCookieService::RemoveCookiesWithOriginAttributes(
    4949             :     const mozilla::OriginAttributesPattern& aPattern,
    4950             :     const nsCString& aBaseDomain)
    4951             : {
    4952           0 :   if (!mDBState) {
    4953           0 :     NS_WARNING("No DBState! Profile already close?");
    4954           0 :     return NS_ERROR_NOT_AVAILABLE;
    4955             :   }
    4956             : 
    4957           0 :   AutoRestore<DBState*> savePrevDBState(mDBState);
    4958           0 :   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
    4959           0 :       aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState;
    4960             : 
    4961           0 :   mozStorageTransaction transaction(mDBState->dbConn, false);
    4962             :   // Iterate the hash table of nsCookieEntry.
    4963           0 :   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
    4964           0 :     nsCookieEntry* entry = iter.Get();
    4965             : 
    4966           0 :     if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
    4967           0 :       continue;
    4968             :     }
    4969             : 
    4970           0 :     if (!aPattern.Matches(entry->mOriginAttributes)) {
    4971           0 :       continue;
    4972             :     }
    4973             : 
    4974             :     // Pattern matches. Delete all cookies within this nsCookieEntry.
    4975           0 :     const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
    4976             : 
    4977           0 :     while (!cookies.IsEmpty()) {
    4978           0 :       nsCookie *cookie = cookies.LastElement();
    4979             : 
    4980           0 :       nsAutoCString host;
    4981           0 :       cookie->GetHost(host);
    4982             : 
    4983           0 :       nsAutoCString name;
    4984           0 :       cookie->GetName(name);
    4985             : 
    4986           0 :       nsAutoCString path;
    4987           0 :       cookie->GetPath(path);
    4988             : 
    4989             :       // Remove the cookie.
    4990           0 :       nsresult rv = Remove(host, entry->mOriginAttributes, name, path, false);
    4991           0 :       NS_ENSURE_SUCCESS(rv, rv);
    4992             :     }
    4993             :   }
    4994           0 :   DebugOnly<nsresult> rv = transaction.Commit();
    4995           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
    4996             : 
    4997           0 :   return NS_OK;
    4998             : }
    4999             : 
    5000             : // find an secure cookie specified by host and name
    5001             : bool
    5002           0 : nsCookieService::FindSecureCookie(const nsCookieKey    &aKey,
    5003             :                                   nsCookie             *aCookie)
    5004             : {
    5005           0 :   EnsureReadDomain(aKey);
    5006             : 
    5007           0 :   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
    5008           0 :   if (!entry)
    5009           0 :     return false;
    5010             : 
    5011           0 :   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
    5012           0 :   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    5013           0 :     nsCookie *cookie = cookies[i];
    5014             :     // isn't a match if insecure or a different name
    5015           0 :     if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name()))
    5016           0 :       continue;
    5017             : 
    5018             :     // The host must "domain-match" an existing cookie or vice-versa
    5019           0 :     if (DomainMatches(cookie, aCookie->Host()) ||
    5020           0 :         DomainMatches(aCookie, cookie->Host())) {
    5021             :       // If the path of new cookie and the path of existing cookie
    5022             :       // aren't "/", then this situation needs to compare paths to
    5023             :       // ensure only that a newly-created non-secure cookie does not
    5024             :       // overlay an existing secure cookie.
    5025           0 :       if (PathMatches(cookie, aCookie->Path())) {
    5026           0 :         return true;
    5027             :       }
    5028             :     }
    5029             :   }
    5030             : 
    5031           0 :   return false;
    5032             : }
    5033             : 
    5034             : // find an exact cookie specified by host, name, and path that hasn't expired.
    5035             : bool
    5036           0 : nsCookieService::FindCookie(const nsCookieKey    &aKey,
    5037             :                             const nsCString& aHost,
    5038             :                             const nsCString& aName,
    5039             :                             const nsCString& aPath,
    5040             :                             nsListIter           &aIter)
    5041             : {
    5042           0 :   EnsureReadDomain(aKey);
    5043             : 
    5044           0 :   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
    5045           0 :   if (!entry)
    5046           0 :     return false;
    5047             : 
    5048           0 :   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
    5049           0 :   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
    5050           0 :     nsCookie *cookie = cookies[i];
    5051             : 
    5052           0 :     if (aHost.Equals(cookie->Host()) &&
    5053           0 :         aPath.Equals(cookie->Path()) &&
    5054           0 :         aName.Equals(cookie->Name())) {
    5055           0 :       aIter = nsListIter(entry, i);
    5056           0 :       return true;
    5057             :     }
    5058             :   }
    5059             : 
    5060           0 :   return false;
    5061             : }
    5062             : 
    5063             : // remove a cookie from the hashtable, and update the iterator state.
    5064             : void
    5065           0 : nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
    5066             :                                       mozIStorageBindingParamsArray *aParamsArray)
    5067             : {
    5068             :   // if it's a non-session cookie, remove it from the db
    5069           0 :   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
    5070             :     // Use the asynchronous binding methods to ensure that we do not acquire
    5071             :     // the database lock.
    5072           0 :     mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
    5073           0 :     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
    5074           0 :     if (!paramsArray) {
    5075           0 :       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
    5076             :     }
    5077             : 
    5078           0 :     nsCOMPtr<mozIStorageBindingParams> params;
    5079           0 :     paramsArray->NewBindingParams(getter_AddRefs(params));
    5080             : 
    5081             :     DebugOnly<nsresult> rv =
    5082           0 :       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
    5083           0 :                                    aIter.Cookie()->Name());
    5084           0 :     NS_ASSERT_SUCCESS(rv);
    5085             : 
    5086           0 :     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
    5087           0 :                                       aIter.Cookie()->Host());
    5088           0 :     NS_ASSERT_SUCCESS(rv);
    5089             : 
    5090           0 :     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
    5091           0 :                                       aIter.Cookie()->Path());
    5092           0 :     NS_ASSERT_SUCCESS(rv);
    5093             : 
    5094           0 :     nsAutoCString suffix;
    5095           0 :     aIter.Cookie()->OriginAttributesRef().CreateSuffix(suffix);
    5096           0 :     rv = params->BindUTF8StringByName(
    5097           0 :       NS_LITERAL_CSTRING("originAttributes"), suffix);
    5098           0 :     NS_ASSERT_SUCCESS(rv);
    5099             : 
    5100           0 :     rv = paramsArray->AddParams(params);
    5101           0 :     NS_ASSERT_SUCCESS(rv);
    5102             : 
    5103             :     // If we weren't given a params array, we'll need to remove it ourselves.
    5104           0 :     if (!aParamsArray) {
    5105           0 :       rv = stmt->BindParameters(paramsArray);
    5106           0 :       NS_ASSERT_SUCCESS(rv);
    5107           0 :       nsCOMPtr<mozIStoragePendingStatement> handle;
    5108           0 :       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
    5109           0 :       NS_ASSERT_SUCCESS(rv);
    5110             :     }
    5111             :   }
    5112             : 
    5113           0 :   if (aIter.entry->GetCookies().Length() == 1) {
    5114             :     // we're removing the last element in the array - so just remove the entry
    5115             :     // from the hash. note that the entryclass' dtor will take care of
    5116             :     // releasing this last element for us!
    5117           0 :     mDBState->hostTable.RawRemoveEntry(aIter.entry);
    5118             : 
    5119             :   } else {
    5120             :     // just remove the element from the list
    5121           0 :     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
    5122             :   }
    5123             : 
    5124           0 :   --mDBState->cookieCount;
    5125           0 : }
    5126             : 
    5127             : void
    5128           0 : bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
    5129             :                      const nsCookieKey &aKey,
    5130             :                      const nsCookie *aCookie)
    5131             : {
    5132           0 :   NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
    5133           0 :   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
    5134             : 
    5135             :   // Use the asynchronous binding methods to ensure that we do not acquire the
    5136             :   // database lock.
    5137           0 :   nsCOMPtr<mozIStorageBindingParams> params;
    5138             :   DebugOnly<nsresult> rv =
    5139           0 :     aParamsArray->NewBindingParams(getter_AddRefs(params));
    5140           0 :   NS_ASSERT_SUCCESS(rv);
    5141             : 
    5142             :   // Bind our values to params
    5143           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
    5144           0 :                                     aKey.mBaseDomain);
    5145           0 :   NS_ASSERT_SUCCESS(rv);
    5146             : 
    5147           0 :   nsAutoCString suffix;
    5148           0 :   aKey.mOriginAttributes.CreateSuffix(suffix);
    5149           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
    5150           0 :                                     suffix);
    5151           0 :   NS_ASSERT_SUCCESS(rv);
    5152             : 
    5153           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
    5154           0 :                                     aCookie->Name());
    5155           0 :   NS_ASSERT_SUCCESS(rv);
    5156             : 
    5157           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
    5158           0 :                                     aCookie->Value());
    5159           0 :   NS_ASSERT_SUCCESS(rv);
    5160             : 
    5161           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
    5162           0 :                                     aCookie->Host());
    5163           0 :   NS_ASSERT_SUCCESS(rv);
    5164             : 
    5165           0 :   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
    5166           0 :                                     aCookie->Path());
    5167           0 :   NS_ASSERT_SUCCESS(rv);
    5168             : 
    5169           0 :   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
    5170           0 :                                aCookie->Expiry());
    5171           0 :   NS_ASSERT_SUCCESS(rv);
    5172             : 
    5173           0 :   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
    5174           0 :                                aCookie->LastAccessed());
    5175           0 :   NS_ASSERT_SUCCESS(rv);
    5176             : 
    5177           0 :   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
    5178           0 :                                aCookie->CreationTime());
    5179           0 :   NS_ASSERT_SUCCESS(rv);
    5180             : 
    5181           0 :   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
    5182           0 :                                aCookie->IsSecure());
    5183           0 :   NS_ASSERT_SUCCESS(rv);
    5184             : 
    5185           0 :   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
    5186           0 :                                aCookie->IsHttpOnly());
    5187           0 :   NS_ASSERT_SUCCESS(rv);
    5188             : 
    5189             :   // Bind the params to the array.
    5190           0 :   rv = aParamsArray->AddParams(params);
    5191           0 :   NS_ASSERT_SUCCESS(rv);
    5192           0 : }
    5193             : 
    5194             : void
    5195           0 : nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
    5196             :                                         nsCookie* aCookie)
    5197             : {
    5198           0 :   if (aCookie->LastAccessed() < aDBState->cookieOldestTime) {
    5199           0 :     aDBState->cookieOldestTime = aCookie->LastAccessed();
    5200             :   }
    5201           0 : }
    5202             : 
    5203             : void
    5204           0 : nsCookieService::AddCookieToList(const nsCookieKey             &aKey,
    5205             :                                  nsCookie                      *aCookie,
    5206             :                                  DBState                       *aDBState,
    5207             :                                  mozIStorageBindingParamsArray *aParamsArray,
    5208             :                                  bool                           aWriteToDB)
    5209             : {
    5210           0 :   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
    5211             :                "Not writing to the DB but have a params array?");
    5212           0 :   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
    5213             :                "Do not have a DB connection but have a params array?");
    5214             : 
    5215           0 :   if (!aCookie) {
    5216           0 :     NS_WARNING("Attempting to AddCookieToList with null cookie");
    5217           0 :     return;
    5218             :   }
    5219             : 
    5220           0 :   nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
    5221           0 :   NS_ASSERTION(entry, "can't insert element into a null entry!");
    5222             : 
    5223           0 :   entry->GetCookies().AppendElement(aCookie);
    5224           0 :   ++aDBState->cookieCount;
    5225             : 
    5226             :   // keep track of the oldest cookie, for when it comes time to purge
    5227           0 :   UpdateCookieOldestTime(aDBState, aCookie);
    5228             : 
    5229             :   // if it's a non-session cookie and hasn't just been read from the db, write it out.
    5230           0 :   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
    5231           0 :     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
    5232           0 :     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
    5233           0 :     if (!paramsArray) {
    5234           0 :       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
    5235             :     }
    5236           0 :     bindCookieParameters(paramsArray, aKey, aCookie);
    5237             : 
    5238             :     // If we were supplied an array to store parameters, we shouldn't call
    5239             :     // executeAsync - someone up the stack will do this for us.
    5240           0 :     if (!aParamsArray) {
    5241           0 :       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
    5242           0 :       NS_ASSERT_SUCCESS(rv);
    5243           0 :       nsCOMPtr<mozIStoragePendingStatement> handle;
    5244           0 :       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
    5245           0 :       NS_ASSERT_SUCCESS(rv);
    5246             :     }
    5247             :   }
    5248             : }
    5249             : 
    5250             : void
    5251           0 : nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
    5252             :                                     int64_t                        aLastAccessed,
    5253             :                                     mozIStorageBindingParamsArray *aParamsArray)
    5254             : {
    5255           0 :   NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
    5256             : 
    5257             :   // udpate the lastAccessed timestamp
    5258           0 :   aCookie->SetLastAccessed(aLastAccessed);
    5259             : 
    5260             :   // if it's a non-session cookie, update it in the db too
    5261           0 :   if (!aCookie->IsSession() && aParamsArray) {
    5262             :     // Create our params holder.
    5263           0 :     nsCOMPtr<mozIStorageBindingParams> params;
    5264           0 :     aParamsArray->NewBindingParams(getter_AddRefs(params));
    5265             : 
    5266             :     // Bind our parameters.
    5267             :     DebugOnly<nsresult> rv =
    5268           0 :       params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
    5269           0 :                               aLastAccessed);
    5270           0 :     NS_ASSERT_SUCCESS(rv);
    5271             : 
    5272           0 :     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
    5273           0 :                                       aCookie->Name());
    5274           0 :     NS_ASSERT_SUCCESS(rv);
    5275             : 
    5276           0 :     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
    5277           0 :                                       aCookie->Host());
    5278           0 :     NS_ASSERT_SUCCESS(rv);
    5279             : 
    5280           0 :     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
    5281           0 :                                       aCookie->Path());
    5282           0 :     NS_ASSERT_SUCCESS(rv);
    5283             : 
    5284           0 :     nsAutoCString suffix;
    5285           0 :     aCookie->OriginAttributesRef().CreateSuffix(suffix);
    5286           0 :     rv = params->BindUTF8StringByName(
    5287           0 :       NS_LITERAL_CSTRING("originAttributes"), suffix);
    5288           0 :     NS_ASSERT_SUCCESS(rv);
    5289             : 
    5290             :     // Add our bound parameters to the array.
    5291           0 :     rv = aParamsArray->AddParams(params);
    5292           0 :     NS_ASSERT_SUCCESS(rv);
    5293             :   }
    5294           0 : }
    5295             : 
    5296             : size_t
    5297           0 : nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
    5298             : {
    5299           0 :   size_t n = aMallocSizeOf(this);
    5300             : 
    5301           0 :   if (mDefaultDBState) {
    5302           0 :     n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
    5303             :   }
    5304           0 :   if (mPrivateDBState) {
    5305           0 :     n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
    5306             :   }
    5307             : 
    5308           0 :   return n;
    5309             : }
    5310             : 
    5311           0 : MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
    5312             : 
    5313             : NS_IMETHODIMP
    5314           0 : nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
    5315             :                                 nsISupports* aData, bool aAnonymize)
    5316             : {
    5317           0 :   MOZ_COLLECT_REPORT(
    5318             :     "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
    5319             :     SizeOfIncludingThis(CookieServiceMallocSizeOf),
    5320           0 :     "Memory used by the cookie service.");
    5321             : 
    5322           0 :   return NS_OK;
    5323             : }

Generated by: LCOV version 1.13