netwerk/cookie/nsCookieService.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
parent 0
6474c204b198
child 7
129ffea94266
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial