netwerk/cookie/nsCookieService.cpp

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

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

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

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

mercurial