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