netwerk/cookie/nsCookieService.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
parent 4
fc2d59ddac77
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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@7 1638 // Double keying: First get the first party URI
michael@4 1639 nsCOMPtr<nsIURI> firstPartyURI;
michael@7 1640 nsAutoCString origDomain;
michael@4 1641 bool requireHostMatch;
michael@7 1642 nsresult rv = mThirdPartyUtil->GetFirstPartyIsolationURI(aChannel, nullptr, getter_AddRefs(firstPartyURI));
michael@7 1643 if (firstPartyURI) {
michael@7 1644 // Double keying: Now get the originating domain
michael@7 1645 rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
michael@7 1646 if (NS_FAILED(rv)) {
michael@7 1647 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
michael@7 1648 "couldn't get base domain from URI");
michael@7 1649 }
michael@4 1650 }
michael@7 1651 //else if (NS_SUCCEEDED(rv)) {}; // Not reached, we got a originating domain!
michael@4 1652
michael@0 1653 nsAutoCString result;
michael@0 1654 GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId,
michael@4 1655 inBrowserElement, isPrivate, origDomain, result);
michael@0 1656 *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
michael@0 1657 return NS_OK;
michael@0 1658 }
michael@0 1659
michael@0 1660 NS_IMETHODIMP
michael@0 1661 nsCookieService::SetCookieString(nsIURI *aHostURI,
michael@0 1662 nsIPrompt *aPrompt,
michael@0 1663 const char *aCookieHeader,
michael@0 1664 nsIChannel *aChannel)
michael@0 1665 {
michael@0 1666 // The aPrompt argument is deprecated and unused. Avoid introducing new
michael@0 1667 // code that uses this argument by warning if the value is non-null.
michael@0 1668 MOZ_ASSERT(!aPrompt);
michael@0 1669 if (aPrompt) {
michael@0 1670 nsCOMPtr<nsIConsoleService> aConsoleService =
michael@0 1671 do_GetService("@mozilla.org/consoleservice;1");
michael@0 1672 if (aConsoleService) {
michael@0 1673 aConsoleService->LogStringMessage(
michael@0 1674 MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
michael@0 1675 }
michael@0 1676 }
michael@0 1677 return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
michael@0 1678 false);
michael@0 1679 }
michael@0 1680
michael@0 1681 NS_IMETHODIMP
michael@0 1682 nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
michael@0 1683 nsIURI *aFirstURI,
michael@0 1684 nsIPrompt *aPrompt,
michael@0 1685 const char *aCookieHeader,
michael@0 1686 const char *aServerTime,
michael@0 1687 nsIChannel *aChannel)
michael@0 1688 {
michael@0 1689 // The aPrompt argument is deprecated and unused. Avoid introducing new
michael@0 1690 // code that uses this argument by warning if the value is non-null.
michael@0 1691 MOZ_ASSERT(!aPrompt);
michael@0 1692 if (aPrompt) {
michael@0 1693 nsCOMPtr<nsIConsoleService> aConsoleService =
michael@0 1694 do_GetService("@mozilla.org/consoleservice;1");
michael@0 1695 if (aConsoleService) {
michael@0 1696 aConsoleService->LogStringMessage(
michael@0 1697 MOZ_UTF16("Non-null prompt ignored by nsCookieService."));
michael@0 1698 }
michael@0 1699 }
michael@0 1700 return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
michael@0 1701 true);
michael@0 1702 }
michael@0 1703
michael@0 1704 nsresult
michael@0 1705 nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
michael@0 1706 const char *aCookieHeader,
michael@0 1707 const char *aServerTime,
michael@0 1708 nsIChannel *aChannel,
michael@0 1709 bool aFromHttp)
michael@0 1710 {
michael@0 1711 NS_ENSURE_ARG(aHostURI);
michael@0 1712 NS_ENSURE_ARG(aCookieHeader);
michael@0 1713
michael@0 1714 // Determine whether the request is foreign. Failure is acceptable.
michael@0 1715 bool isForeign = true;
michael@0 1716 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
michael@0 1717
michael@0 1718 // Get app info, if channel is present. Else assume default namespace.
michael@0 1719 uint32_t appId = NECKO_NO_APP_ID;
michael@0 1720 bool inBrowserElement = false;
michael@0 1721 if (aChannel) {
michael@0 1722 NS_GetAppInfo(aChannel, &appId, &inBrowserElement);
michael@0 1723 }
michael@0 1724
michael@0 1725 bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
michael@0 1726
michael@0 1727 nsDependentCString cookieString(aCookieHeader);
michael@0 1728 nsDependentCString serverTime(aServerTime ? aServerTime : "");
michael@0 1729 SetCookieStringInternal(aHostURI, isForeign, cookieString,
michael@0 1730 serverTime, aFromHttp, appId, inBrowserElement,
michael@0 1731 isPrivate, aChannel);
michael@0 1732 return NS_OK;
michael@0 1733 }
michael@0 1734
michael@4 1735 // FIXME:MSvB DEBUG DEBUG - DELETEME DELETEME - debug debug - deleteme deleteme
michael@4 1736 // FIXME:MSvB Setting a 3rd party cookie (on third.tld) for URL bar browsed
michael@4 1737 // FIXME:MSvB site first.tld causes aHostURI (and later the origin var) to
michael@4 1738 // FIXME:MSvB contain 'third.tld'
michael@0 1739 void
michael@0 1740 nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
michael@0 1741 bool aIsForeign,
michael@0 1742 nsDependentCString &aCookieHeader,
michael@0 1743 const nsCString &aServerTime,
michael@0 1744 bool aFromHttp,
michael@0 1745 uint32_t aAppId,
michael@0 1746 bool aInBrowserElement,
michael@0 1747 bool aIsPrivate,
michael@0 1748 nsIChannel *aChannel)
michael@0 1749 {
michael@0 1750 NS_ASSERTION(aHostURI, "null host!");
michael@0 1751
michael@0 1752 if (!mDBState) {
michael@0 1753 NS_WARNING("No DBState! Profile already closed?");
michael@0 1754 return;
michael@0 1755 }
michael@0 1756
michael@0 1757 AutoRestore<DBState*> savePrevDBState(mDBState);
michael@0 1758 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
michael@0 1759
michael@0 1760 // get the base domain for the host URI.
michael@0 1761 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
michael@0 1762 // file:// URI's (i.e. with an empty host) are allowed, but any other
michael@0 1763 // scheme must have a non-empty host. A trailing dot in the host
michael@0 1764 // is acceptable.
michael@0 1765 bool requireHostMatch;
michael@0 1766 nsAutoCString baseDomain;
michael@0 1767 nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
michael@0 1768 if (NS_FAILED(rv)) {
michael@0 1769 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 1770 "couldn't get base domain from URI");
michael@0 1771 return;
michael@0 1772 }
michael@0 1773
michael@0 1774 nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
michael@0 1775
michael@0 1776 // check default prefs
michael@0 1777 CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
michael@0 1778 aCookieHeader.get());
michael@0 1779 // fire a notification if third party or if cookie was rejected
michael@0 1780 // (but not if there was an error)
michael@0 1781 switch (cookieStatus) {
michael@0 1782 case STATUS_REJECTED:
michael@0 1783 NotifyRejected(aHostURI);
michael@0 1784 if (aIsForeign) {
michael@0 1785 NotifyThirdParty(aHostURI, false, aChannel);
michael@0 1786 }
michael@0 1787 return; // Stop here
michael@0 1788 case STATUS_REJECTED_WITH_ERROR:
michael@0 1789 return;
michael@0 1790 case STATUS_ACCEPTED: // Fallthrough
michael@0 1791 case STATUS_ACCEPT_SESSION:
michael@0 1792 if (aIsForeign) {
michael@0 1793 NotifyThirdParty(aHostURI, true, aChannel);
michael@0 1794 }
michael@0 1795 break;
michael@0 1796 default:
michael@0 1797 break;
michael@0 1798 }
michael@0 1799
michael@0 1800 // parse server local time. this is not just done here for efficiency
michael@0 1801 // reasons - if there's an error parsing it, and we need to default it
michael@0 1802 // to the current time, we must do it here since the current time in
michael@0 1803 // SetCookieInternal() will change for each cookie processed (e.g. if the
michael@0 1804 // user is prompted).
michael@0 1805 PRTime tempServerTime;
michael@0 1806 int64_t serverTime;
michael@0 1807 PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
michael@0 1808 &tempServerTime);
michael@0 1809 if (result == PR_SUCCESS) {
michael@0 1810 serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
michael@0 1811 } else {
michael@0 1812 serverTime = PR_Now() / PR_USEC_PER_SEC;
michael@0 1813 }
michael@0 1814
michael@4 1815 // double keyed cookie boilerplate
michael@4 1816 nsCOMPtr<nsIURI> firstPartyURI;
michael@7 1817 mThirdPartyUtil->GetFirstPartyURIFromChannel(aChannel, true, getter_AddRefs(firstPartyURI));
michael@7 1818 NS_ASSERTION(firstPartyURI, "couldn't get the first party URI");
michael@4 1819 nsAutoCString origDomain;
michael@4 1820 rv = GetBaseDomain(firstPartyURI, origDomain, requireHostMatch);
michael@4 1821 if (NS_FAILED(rv)) {
michael@4 1822 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
michael@4 1823 "couldn't get base domain from URI");
michael@4 1824 }
michael@4 1825
michael@0 1826 // process each cookie in the header
michael@4 1827 while (SetCookieInternal(aHostURI, key, requireHostMatch, origDomain,
michael@4 1828 cookieStatus, aCookieHeader, serverTime,
michael@4 1829 aFromHttp, aChannel)) {
michael@0 1830 // document.cookie can only set one cookie at a time
michael@0 1831 if (!aFromHttp)
michael@0 1832 break;
michael@0 1833 }
michael@0 1834 }
michael@0 1835
michael@0 1836 // notify observers that a cookie was rejected due to the users' prefs.
michael@0 1837 void
michael@0 1838 nsCookieService::NotifyRejected(nsIURI *aHostURI)
michael@0 1839 {
michael@0 1840 if (mObserverService) {
michael@0 1841 mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
michael@0 1842 }
michael@0 1843 }
michael@0 1844
michael@0 1845 // notify observers that a third-party cookie was accepted/rejected
michael@0 1846 // if the cookie issuer is unknown, it defaults to "?"
michael@0 1847 void
michael@0 1848 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
michael@0 1849 {
michael@0 1850 if (!mObserverService) {
michael@0 1851 return;
michael@0 1852 }
michael@0 1853
michael@0 1854 const char* topic;
michael@0 1855
michael@0 1856 if (mDBState != mPrivateDBState) {
michael@0 1857 // Regular (non-private) browsing
michael@0 1858 if (aIsAccepted) {
michael@0 1859 topic = "third-party-cookie-accepted";
michael@0 1860 } else {
michael@0 1861 topic = "third-party-cookie-rejected";
michael@0 1862 }
michael@0 1863 } else {
michael@0 1864 // Private browsing
michael@0 1865 if (aIsAccepted) {
michael@0 1866 topic = "private-third-party-cookie-accepted";
michael@0 1867 } else {
michael@0 1868 topic = "private-third-party-cookie-rejected";
michael@0 1869 }
michael@0 1870 }
michael@0 1871
michael@0 1872 do {
michael@0 1873 // Attempt to find the host of aChannel.
michael@0 1874 if (!aChannel) {
michael@0 1875 break;
michael@0 1876 }
michael@0 1877 nsCOMPtr<nsIURI> channelURI;
michael@0 1878 nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
michael@0 1879 if (NS_FAILED(rv)) {
michael@0 1880 break;
michael@0 1881 }
michael@0 1882
michael@0 1883 nsAutoCString referringHost;
michael@0 1884 rv = channelURI->GetHost(referringHost);
michael@0 1885 if (NS_FAILED(rv)) {
michael@0 1886 break;
michael@0 1887 }
michael@0 1888
michael@0 1889 nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
michael@0 1890 mObserverService->NotifyObservers(aHostURI,
michael@0 1891 topic,
michael@0 1892 referringHostUTF16.get());
michael@0 1893 return;
michael@0 1894 } while (0);
michael@0 1895
michael@0 1896 // This can fail for a number of reasons, in which kind we fallback to "?"
michael@0 1897 mObserverService->NotifyObservers(aHostURI,
michael@0 1898 topic,
michael@0 1899 MOZ_UTF16("?"));
michael@0 1900 }
michael@0 1901
michael@0 1902 // notify observers that the cookie list changed. there are five possible
michael@0 1903 // values for aData:
michael@0 1904 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
michael@0 1905 // "added" means a cookie was added. aSubject is the added cookie.
michael@0 1906 // "changed" means a cookie was altered. aSubject is the new cookie.
michael@0 1907 // "cleared" means the entire cookie list was cleared. aSubject is null.
michael@0 1908 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
michael@0 1909 // cookies.
michael@0 1910 void
michael@0 1911 nsCookieService::NotifyChanged(nsISupports *aSubject,
michael@0 1912 const char16_t *aData)
michael@0 1913 {
michael@0 1914 const char* topic = mDBState == mPrivateDBState ?
michael@0 1915 "private-cookie-changed" : "cookie-changed";
michael@0 1916 if (mObserverService)
michael@0 1917 mObserverService->NotifyObservers(aSubject, topic, aData);
michael@0 1918 }
michael@0 1919
michael@0 1920 already_AddRefed<nsIArray>
michael@0 1921 nsCookieService::CreatePurgeList(nsICookie2* aCookie)
michael@0 1922 {
michael@0 1923 nsCOMPtr<nsIMutableArray> removedList =
michael@0 1924 do_CreateInstance(NS_ARRAY_CONTRACTID);
michael@0 1925 removedList->AppendElement(aCookie, false);
michael@0 1926 return removedList.forget();
michael@0 1927 }
michael@0 1928
michael@0 1929 /******************************************************************************
michael@0 1930 * nsCookieService:
michael@0 1931 * pref observer impl
michael@0 1932 ******************************************************************************/
michael@0 1933
michael@0 1934 void
michael@0 1935 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
michael@0 1936 {
michael@0 1937 int32_t val;
michael@0 1938 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
michael@0 1939 mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
michael@0 1940
michael@0 1941 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
michael@0 1942 mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
michael@0 1943
michael@0 1944 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
michael@0 1945 mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
michael@0 1946
michael@0 1947 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
michael@0 1948 mCookiePurgeAge =
michael@0 1949 int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
michael@0 1950 }
michael@0 1951
michael@0 1952 bool boolval;
michael@0 1953 if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
michael@0 1954 mThirdPartySession = boolval;
michael@0 1955 }
michael@0 1956
michael@0 1957 /******************************************************************************
michael@0 1958 * nsICookieManager impl:
michael@0 1959 * nsICookieManager
michael@0 1960 ******************************************************************************/
michael@0 1961
michael@0 1962 NS_IMETHODIMP
michael@0 1963 nsCookieService::RemoveAll()
michael@0 1964 {
michael@0 1965 if (!mDBState) {
michael@0 1966 NS_WARNING("No DBState! Profile already closed?");
michael@0 1967 return NS_ERROR_NOT_AVAILABLE;
michael@0 1968 }
michael@0 1969
michael@0 1970 RemoveAllFromMemory();
michael@0 1971
michael@0 1972 // clear the cookie file
michael@0 1973 if (mDBState->dbConn) {
michael@0 1974 NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
michael@0 1975
michael@0 1976 // Cancel any pending read. No further results will be received by our
michael@0 1977 // read listener.
michael@0 1978 if (mDefaultDBState->pendingRead) {
michael@0 1979 CancelAsyncRead(true);
michael@0 1980 }
michael@0 1981
michael@0 1982 nsCOMPtr<mozIStorageAsyncStatement> stmt;
michael@0 1983 nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
michael@0 1984 "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
michael@0 1985 if (NS_SUCCEEDED(rv)) {
michael@0 1986 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 1987 rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
michael@0 1988 getter_AddRefs(handle));
michael@0 1989 NS_ASSERT_SUCCESS(rv);
michael@0 1990 } else {
michael@0 1991 // Recreate the database.
michael@0 1992 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 1993 ("RemoveAll(): corruption detected with rv 0x%x", rv));
michael@0 1994 HandleCorruptDB(mDefaultDBState);
michael@0 1995 }
michael@0 1996 }
michael@0 1997
michael@0 1998 NotifyChanged(nullptr, MOZ_UTF16("cleared"));
michael@0 1999 return NS_OK;
michael@0 2000 }
michael@0 2001
michael@0 2002 static PLDHashOperator
michael@0 2003 COMArrayCallback(nsCookieEntry *aEntry,
michael@0 2004 void *aArg)
michael@0 2005 {
michael@0 2006 nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
michael@0 2007
michael@0 2008 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
michael@0 2009 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 2010 data->AppendObject(cookies[i]);
michael@0 2011 }
michael@0 2012
michael@0 2013 return PL_DHASH_NEXT;
michael@0 2014 }
michael@0 2015
michael@0 2016 NS_IMETHODIMP
michael@0 2017 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
michael@0 2018 {
michael@0 2019 if (!mDBState) {
michael@0 2020 NS_WARNING("No DBState! Profile already closed?");
michael@0 2021 return NS_ERROR_NOT_AVAILABLE;
michael@0 2022 }
michael@0 2023
michael@0 2024 EnsureReadComplete();
michael@0 2025
michael@0 2026 nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
michael@0 2027 mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
michael@0 2028
michael@0 2029 return NS_NewArrayEnumerator(aEnumerator, cookieList);
michael@0 2030 }
michael@0 2031
michael@0 2032 NS_IMETHODIMP
michael@0 2033 nsCookieService::Add(const nsACString &aHost,
michael@0 2034 const nsACString &aPath,
michael@0 2035 const nsACString &aName,
michael@0 2036 const nsACString &aValue,
michael@0 2037 bool aIsSecure,
michael@0 2038 bool aIsHttpOnly,
michael@0 2039 bool aIsSession,
michael@0 2040 int64_t aExpiry)
michael@0 2041 {
michael@0 2042 if (!mDBState) {
michael@0 2043 NS_WARNING("No DBState! Profile already closed?");
michael@0 2044 return NS_ERROR_NOT_AVAILABLE;
michael@0 2045 }
michael@0 2046
michael@0 2047 // first, normalize the hostname, and fail if it contains illegal characters.
michael@0 2048 nsAutoCString host(aHost);
michael@0 2049 nsresult rv = NormalizeHost(host);
michael@0 2050 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2051
michael@0 2052 // get the base domain for the host URI.
michael@0 2053 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
michael@0 2054 nsAutoCString baseDomain;
michael@0 2055 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 2056 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2057
michael@0 2058 int64_t currentTimeInUsec = PR_Now();
michael@0 2059
michael@0 2060 nsRefPtr<nsCookie> cookie =
michael@4 2061 nsCookie::Create(aName, aValue, host, baseDomain, aPath,
michael@0 2062 aExpiry,
michael@0 2063 currentTimeInUsec,
michael@0 2064 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
michael@0 2065 aIsSession,
michael@0 2066 aIsSecure,
michael@0 2067 aIsHttpOnly);
michael@0 2068 if (!cookie) {
michael@0 2069 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2070 }
michael@0 2071
michael@0 2072 AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
michael@0 2073 return NS_OK;
michael@0 2074 }
michael@0 2075
michael@0 2076
michael@0 2077 nsresult
michael@0 2078 nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId,
michael@0 2079 bool aInBrowserElement, const nsACString& aName,
michael@0 2080 const nsACString& aPath, bool aBlocked)
michael@0 2081 {
michael@0 2082 if (!mDBState) {
michael@0 2083 NS_WARNING("No DBState! Profile already closed?");
michael@0 2084 return NS_ERROR_NOT_AVAILABLE;
michael@0 2085 }
michael@0 2086
michael@0 2087 // first, normalize the hostname, and fail if it contains illegal characters.
michael@0 2088 nsAutoCString host(aHost);
michael@0 2089 nsresult rv = NormalizeHost(host);
michael@0 2090 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2091
michael@0 2092 nsAutoCString baseDomain;
michael@0 2093 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 2094 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2095
michael@0 2096 nsListIter matchIter;
michael@0 2097 nsRefPtr<nsCookie> cookie;
michael@0 2098 if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement),
michael@4 2099 baseDomain,
michael@0 2100 host,
michael@0 2101 PromiseFlatCString(aName),
michael@0 2102 PromiseFlatCString(aPath),
michael@0 2103 matchIter)) {
michael@0 2104 cookie = matchIter.Cookie();
michael@0 2105 RemoveCookieFromList(matchIter);
michael@0 2106 }
michael@0 2107
michael@0 2108 // check if we need to add the host to the permissions blacklist.
michael@0 2109 if (aBlocked && mPermissionService) {
michael@0 2110 // strip off the domain dot, if necessary
michael@0 2111 if (!host.IsEmpty() && host.First() == '.')
michael@0 2112 host.Cut(0, 1);
michael@0 2113
michael@0 2114 host.Insert(NS_LITERAL_CSTRING("http://"), 0);
michael@0 2115
michael@0 2116 nsCOMPtr<nsIURI> uri;
michael@0 2117 NS_NewURI(getter_AddRefs(uri), host);
michael@0 2118
michael@0 2119 if (uri)
michael@0 2120 mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
michael@0 2121 }
michael@0 2122
michael@0 2123 if (cookie) {
michael@0 2124 // Everything's done. Notify observers.
michael@0 2125 NotifyChanged(cookie, MOZ_UTF16("deleted"));
michael@0 2126 }
michael@0 2127
michael@0 2128 return NS_OK;
michael@0 2129 }
michael@0 2130
michael@0 2131 NS_IMETHODIMP
michael@0 2132 nsCookieService::Remove(const nsACString &aHost,
michael@0 2133 const nsACString &aName,
michael@0 2134 const nsACString &aPath,
michael@0 2135 bool aBlocked)
michael@0 2136 {
michael@0 2137 return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked);
michael@0 2138 }
michael@0 2139
michael@0 2140 /******************************************************************************
michael@0 2141 * nsCookieService impl:
michael@0 2142 * private file I/O functions
michael@0 2143 ******************************************************************************/
michael@0 2144
michael@0 2145 // Begin an asynchronous read from the database.
michael@0 2146 OpenDBResult
michael@0 2147 nsCookieService::Read()
michael@0 2148 {
michael@0 2149 // Set up a statement for the read. Note that our query specifies that
michael@0 2150 // 'baseDomain' not be nullptr -- see below for why.
michael@0 2151 nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
michael@0 2152 nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
michael@0 2153 "SELECT "
michael@0 2154 "name, "
michael@0 2155 "value, "
michael@0 2156 "host, "
michael@0 2157 "path, "
michael@0 2158 "expiry, "
michael@0 2159 "lastAccessed, "
michael@0 2160 "creationTime, "
michael@0 2161 "isSecure, "
michael@0 2162 "isHttpOnly, "
michael@0 2163 "baseDomain, "
michael@0 2164 "appId, "
michael@0 2165 "inBrowserElement "
michael@0 2166 "FROM moz_cookies "
michael@0 2167 "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
michael@0 2168 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
michael@0 2169
michael@0 2170 // Set up a statement to delete any rows with a nullptr 'baseDomain'
michael@0 2171 // column. This takes care of any cookies set by browsers that don't
michael@0 2172 // understand the 'baseDomain' column, where the database schema version
michael@0 2173 // is from one that does. (This would occur when downgrading.)
michael@0 2174 nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
michael@0 2175 rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
michael@0 2176 "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
michael@0 2177 getter_AddRefs(stmtDeleteNull));
michael@0 2178 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
michael@0 2179
michael@0 2180 // Start a new connection for sync reads, to reduce contention with the
michael@0 2181 // background thread. We need to do this before we kick off write statements,
michael@0 2182 // since they can lock the database and prevent connections from being opened.
michael@0 2183 rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
michael@0 2184 getter_AddRefs(mDefaultDBState->syncConn));
michael@0 2185 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
michael@0 2186
michael@0 2187 // Init our readSet hash and execute the statements. Note that, after this
michael@0 2188 // point, we cannot fail without altering the cleanup code in InitDBStates()
michael@0 2189 // to handle closing of the now-asynchronous connection.
michael@0 2190 mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
michael@0 2191
michael@0 2192 mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
michael@0 2193 rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
michael@0 2194 getter_AddRefs(mDefaultDBState->pendingRead));
michael@0 2195 NS_ASSERT_SUCCESS(rv);
michael@0 2196
michael@0 2197 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 2198 rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
michael@0 2199 getter_AddRefs(handle));
michael@0 2200 NS_ASSERT_SUCCESS(rv);
michael@0 2201
michael@0 2202 return RESULT_OK;
michael@0 2203 }
michael@0 2204
michael@0 2205 // Extract data from a single result row and create an nsCookie.
michael@0 2206 // This is templated since 'T' is different for sync vs async results.
michael@0 2207 template<class T> nsCookie*
michael@0 2208 nsCookieService::GetCookieFromRow(T &aRow)
michael@0 2209 {
michael@0 2210 // Skip reading 'baseDomain' -- up to the caller.
michael@0 2211 nsCString name, value, host, path;
michael@0 2212 DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
michael@0 2213 NS_ASSERT_SUCCESS(rv);
michael@0 2214 rv = aRow->GetUTF8String(IDX_VALUE, value);
michael@0 2215 NS_ASSERT_SUCCESS(rv);
michael@0 2216 rv = aRow->GetUTF8String(IDX_HOST, host);
michael@0 2217 NS_ASSERT_SUCCESS(rv);
michael@0 2218 rv = aRow->GetUTF8String(IDX_PATH, path);
michael@0 2219 NS_ASSERT_SUCCESS(rv);
michael@0 2220
michael@0 2221 int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
michael@0 2222 int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
michael@0 2223 int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
michael@0 2224 bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
michael@0 2225 bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
michael@0 2226
michael@4 2227 nsAutoCString baseDomain;
michael@4 2228 rv = GetBaseDomainFromHost(host, baseDomain);
michael@4 2229 NS_ASSERT_SUCCESS(rv);
michael@4 2230
michael@0 2231 // Create a new nsCookie and assign the data.
michael@4 2232 return nsCookie::Create(name, value, host, baseDomain, path,
michael@0 2233 expiry,
michael@0 2234 lastAccessed,
michael@0 2235 creationTime,
michael@0 2236 false,
michael@0 2237 isSecure,
michael@0 2238 isHttpOnly);
michael@0 2239 }
michael@0 2240
michael@0 2241 void
michael@0 2242 nsCookieService::AsyncReadComplete()
michael@0 2243 {
michael@0 2244 // We may be in the private browsing DB state, with a pending read on the
michael@0 2245 // default DB state. (This would occur if we started up in private browsing
michael@0 2246 // mode.) As long as we do all our operations on the default state, we're OK.
michael@0 2247 NS_ASSERTION(mDefaultDBState, "no default DBState");
michael@0 2248 NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
michael@0 2249 NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
michael@0 2250
michael@0 2251 // Merge the data read on the background thread with the data synchronously
michael@0 2252 // read on the main thread. Note that transactions on the cookie table may
michael@0 2253 // have occurred on the main thread since, making the background data stale.
michael@0 2254 for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
michael@0 2255 const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
michael@0 2256
michael@0 2257 // Tiebreak: if the given base domain has already been read in, ignore
michael@0 2258 // the background data. Note that readSet may contain domains that were
michael@0 2259 // queried but found not to be in the db -- that's harmless.
michael@0 2260 if (mDefaultDBState->readSet.GetEntry(tuple.key))
michael@0 2261 continue;
michael@0 2262
michael@0 2263 AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
michael@0 2264 }
michael@0 2265
michael@0 2266 mDefaultDBState->stmtReadDomain = nullptr;
michael@0 2267 mDefaultDBState->pendingRead = nullptr;
michael@0 2268 mDefaultDBState->readListener = nullptr;
michael@0 2269 mDefaultDBState->syncConn = nullptr;
michael@0 2270 mDefaultDBState->hostArray.Clear();
michael@0 2271 mDefaultDBState->readSet.Clear();
michael@0 2272
michael@0 2273 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
michael@0 2274 mDefaultDBState->cookieCount));
michael@0 2275
michael@0 2276 mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
michael@0 2277 }
michael@0 2278
michael@0 2279 void
michael@0 2280 nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
michael@0 2281 {
michael@0 2282 // We may be in the private browsing DB state, with a pending read on the
michael@0 2283 // default DB state. (This would occur if we started up in private browsing
michael@0 2284 // mode.) As long as we do all our operations on the default state, we're OK.
michael@0 2285 NS_ASSERTION(mDefaultDBState, "no default DBState");
michael@0 2286 NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
michael@0 2287 NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
michael@0 2288
michael@0 2289 // Cancel the pending read, kill the read listener, and empty the array
michael@0 2290 // of data already read in on the background thread.
michael@0 2291 mDefaultDBState->readListener->Cancel();
michael@0 2292 DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
michael@0 2293 NS_ASSERT_SUCCESS(rv);
michael@0 2294
michael@0 2295 mDefaultDBState->stmtReadDomain = nullptr;
michael@0 2296 mDefaultDBState->pendingRead = nullptr;
michael@0 2297 mDefaultDBState->readListener = nullptr;
michael@0 2298 mDefaultDBState->hostArray.Clear();
michael@0 2299
michael@0 2300 // Only clear the 'readSet' table if we no longer need to know what set of
michael@0 2301 // data is already accounted for.
michael@0 2302 if (aPurgeReadSet)
michael@0 2303 mDefaultDBState->readSet.Clear();
michael@0 2304 }
michael@0 2305
michael@0 2306 void
michael@0 2307 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
michael@0 2308 {
michael@0 2309 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
michael@0 2310 "not in default db state");
michael@0 2311
michael@0 2312 // Fast path 1: nothing to read, or we've already finished reading.
michael@0 2313 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
michael@0 2314 return;
michael@0 2315
michael@0 2316 // Fast path 2: already read in this particular domain.
michael@0 2317 if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
michael@0 2318 return;
michael@0 2319
michael@0 2320 // Read in the data synchronously.
michael@0 2321 // see IDX_NAME, etc. for parameter indexes
michael@0 2322 nsresult rv;
michael@0 2323 if (!mDefaultDBState->stmtReadDomain) {
michael@0 2324 // Cache the statement, since it's likely to be used again.
michael@0 2325 rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
michael@0 2326 "SELECT "
michael@0 2327 "name, "
michael@0 2328 "value, "
michael@0 2329 "host, "
michael@0 2330 "path, "
michael@0 2331 "expiry, "
michael@0 2332 "lastAccessed, "
michael@0 2333 "creationTime, "
michael@0 2334 "isSecure, "
michael@0 2335 "isHttpOnly "
michael@0 2336 "FROM moz_cookies "
michael@0 2337 "WHERE baseDomain = :baseDomain "
michael@0 2338 " AND appId = :appId "
michael@0 2339 " AND inBrowserElement = :inBrowserElement"),
michael@0 2340 getter_AddRefs(mDefaultDBState->stmtReadDomain));
michael@0 2341
michael@0 2342 if (NS_FAILED(rv)) {
michael@0 2343 // Recreate the database.
michael@0 2344 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2345 ("EnsureReadDomain(): corruption detected when creating statement "
michael@0 2346 "with rv 0x%x", rv));
michael@0 2347 HandleCorruptDB(mDefaultDBState);
michael@0 2348 return;
michael@0 2349 }
michael@0 2350 }
michael@0 2351
michael@0 2352 NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
michael@0 2353
michael@0 2354 mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
michael@0 2355
michael@0 2356 rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
michael@0 2357 NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
michael@0 2358 NS_ASSERT_SUCCESS(rv);
michael@0 2359 rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
michael@0 2360 NS_LITERAL_CSTRING("appId"), aKey.mAppId);
michael@0 2361 NS_ASSERT_SUCCESS(rv);
michael@0 2362 rv = mDefaultDBState->stmtReadDomain->BindInt32ByName(
michael@0 2363 NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0);
michael@0 2364 NS_ASSERT_SUCCESS(rv);
michael@0 2365
michael@0 2366
michael@0 2367 bool hasResult;
michael@0 2368 nsCString name, value, host, path;
michael@0 2369 nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array;
michael@0 2370 while (1) {
michael@0 2371 rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
michael@0 2372 if (NS_FAILED(rv)) {
michael@0 2373 // Recreate the database.
michael@0 2374 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2375 ("EnsureReadDomain(): corruption detected when reading result "
michael@0 2376 "with rv 0x%x", rv));
michael@0 2377 HandleCorruptDB(mDefaultDBState);
michael@0 2378 return;
michael@0 2379 }
michael@0 2380
michael@0 2381 if (!hasResult)
michael@0 2382 break;
michael@0 2383
michael@0 2384 array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
michael@0 2385 }
michael@0 2386
michael@0 2387 // Add the cookies to the table in a single operation. This makes sure that
michael@0 2388 // either all the cookies get added, or in the case of corruption, none.
michael@0 2389 for (uint32_t i = 0; i < array.Length(); ++i) {
michael@0 2390 AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
michael@0 2391 }
michael@0 2392
michael@0 2393 // Add it to the hashset of read entries, so we don't read it again.
michael@0 2394 mDefaultDBState->readSet.PutEntry(aKey);
michael@0 2395
michael@0 2396 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2397 ("EnsureReadDomain(): %ld cookies read for base domain %s, "
michael@0 2398 " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(),
michael@0 2399 (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement));
michael@0 2400 }
michael@0 2401
michael@0 2402 void
michael@0 2403 nsCookieService::EnsureReadComplete()
michael@0 2404 {
michael@0 2405 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
michael@0 2406 "not in default db state");
michael@0 2407
michael@0 2408 // Fast path 1: nothing to read, or we've already finished reading.
michael@0 2409 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
michael@0 2410 return;
michael@0 2411
michael@0 2412 // Cancel the pending read, so we don't get any more results.
michael@0 2413 CancelAsyncRead(false);
michael@0 2414
michael@0 2415 // Read in the data synchronously.
michael@0 2416 // see IDX_NAME, etc. for parameter indexes
michael@0 2417 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 2418 nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
michael@0 2419 "SELECT "
michael@0 2420 "name, "
michael@0 2421 "value, "
michael@0 2422 "host, "
michael@0 2423 "path, "
michael@0 2424 "expiry, "
michael@0 2425 "lastAccessed, "
michael@0 2426 "creationTime, "
michael@0 2427 "isSecure, "
michael@0 2428 "isHttpOnly, "
michael@0 2429 "baseDomain, "
michael@0 2430 "appId, "
michael@0 2431 "inBrowserElement "
michael@0 2432 "FROM moz_cookies "
michael@0 2433 "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
michael@0 2434
michael@0 2435 if (NS_FAILED(rv)) {
michael@0 2436 // Recreate the database.
michael@0 2437 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2438 ("EnsureReadComplete(): corruption detected when creating statement "
michael@0 2439 "with rv 0x%x", rv));
michael@0 2440 HandleCorruptDB(mDefaultDBState);
michael@0 2441 return;
michael@0 2442 }
michael@0 2443
michael@0 2444 nsCString baseDomain, name, value, host, path;
michael@0 2445 uint32_t appId;
michael@0 2446 bool inBrowserElement, hasResult;
michael@0 2447 nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array;
michael@0 2448 while (1) {
michael@0 2449 rv = stmt->ExecuteStep(&hasResult);
michael@0 2450 if (NS_FAILED(rv)) {
michael@0 2451 // Recreate the database.
michael@0 2452 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2453 ("EnsureReadComplete(): corruption detected when reading result "
michael@0 2454 "with rv 0x%x", rv));
michael@0 2455 HandleCorruptDB(mDefaultDBState);
michael@0 2456 return;
michael@0 2457 }
michael@0 2458
michael@0 2459 if (!hasResult)
michael@0 2460 break;
michael@0 2461
michael@0 2462 // Make sure we haven't already read the data.
michael@0 2463 stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
michael@0 2464 appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID));
michael@0 2465 inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM));
michael@0 2466 nsCookieKey key(baseDomain, appId, inBrowserElement);
michael@0 2467 if (mDefaultDBState->readSet.GetEntry(key))
michael@0 2468 continue;
michael@0 2469
michael@0 2470 CookieDomainTuple* tuple = array.AppendElement();
michael@0 2471 tuple->key = key;
michael@0 2472 tuple->cookie = GetCookieFromRow(stmt);
michael@0 2473 }
michael@0 2474
michael@0 2475 // Add the cookies to the table in a single operation. This makes sure that
michael@0 2476 // either all the cookies get added, or in the case of corruption, none.
michael@0 2477 for (uint32_t i = 0; i < array.Length(); ++i) {
michael@0 2478 CookieDomainTuple& tuple = array[i];
michael@0 2479 AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
michael@0 2480 false);
michael@0 2481 }
michael@0 2482
michael@0 2483 mDefaultDBState->syncConn = nullptr;
michael@0 2484 mDefaultDBState->readSet.Clear();
michael@0 2485
michael@0 2486 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 2487 ("EnsureReadComplete(): %ld cookies read", array.Length()));
michael@0 2488 }
michael@0 2489
michael@0 2490 NS_IMETHODIMP
michael@0 2491 nsCookieService::ImportCookies(nsIFile *aCookieFile)
michael@0 2492 {
michael@0 2493 if (!mDBState) {
michael@0 2494 NS_WARNING("No DBState! Profile already closed?");
michael@0 2495 return NS_ERROR_NOT_AVAILABLE;
michael@0 2496 }
michael@0 2497
michael@0 2498 // Make sure we're in the default DB state. We don't want people importing
michael@0 2499 // cookies into a private browsing session!
michael@0 2500 if (mDBState != mDefaultDBState) {
michael@0 2501 NS_WARNING("Trying to import cookies in a private browsing session!");
michael@0 2502 return NS_ERROR_NOT_AVAILABLE;
michael@0 2503 }
michael@0 2504
michael@0 2505 nsresult rv;
michael@0 2506 nsCOMPtr<nsIInputStream> fileInputStream;
michael@0 2507 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
michael@0 2508 if (NS_FAILED(rv)) return rv;
michael@0 2509
michael@0 2510 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
michael@0 2511 if (NS_FAILED(rv)) return rv;
michael@0 2512
michael@0 2513 // First, ensure we've read in everything from the database, if we have one.
michael@0 2514 EnsureReadComplete();
michael@0 2515
michael@0 2516 static const char kTrue[] = "TRUE";
michael@0 2517
michael@0 2518 nsAutoCString buffer, baseDomain;
michael@0 2519 bool isMore = true;
michael@0 2520 int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
michael@0 2521 nsASingleFragmentCString::char_iterator iter;
michael@0 2522 int32_t numInts;
michael@0 2523 int64_t expires;
michael@0 2524 bool isDomain, isHttpOnly = false;
michael@0 2525 uint32_t originalCookieCount = mDefaultDBState->cookieCount;
michael@0 2526
michael@0 2527 int64_t currentTimeInUsec = PR_Now();
michael@0 2528 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
michael@0 2529 // we use lastAccessedCounter to keep cookies in recently-used order,
michael@0 2530 // so we start by initializing to currentTime (somewhat arbitrary)
michael@0 2531 int64_t lastAccessedCounter = currentTimeInUsec;
michael@0 2532
michael@0 2533 /* file format is:
michael@0 2534 *
michael@0 2535 * host \t isDomain \t path \t secure \t expires \t name \t cookie
michael@0 2536 *
michael@0 2537 * if this format isn't respected we move onto the next line in the file.
michael@0 2538 * isDomain is "TRUE" or "FALSE" (default to "FALSE")
michael@0 2539 * isSecure is "TRUE" or "FALSE" (default to "TRUE")
michael@0 2540 * expires is a int64_t integer
michael@0 2541 * note 1: cookie can contain tabs.
michael@0 2542 * note 2: cookies will be stored in order of lastAccessed time:
michael@0 2543 * most-recently used come first; least-recently-used come last.
michael@0 2544 */
michael@0 2545
michael@0 2546 /*
michael@0 2547 * ...but due to bug 178933, we hide HttpOnly cookies from older code
michael@0 2548 * in a comment, so they don't expose HttpOnly cookies to JS.
michael@0 2549 *
michael@0 2550 * The format for HttpOnly cookies is
michael@0 2551 *
michael@0 2552 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
michael@0 2553 *
michael@0 2554 */
michael@0 2555
michael@0 2556 // We will likely be adding a bunch of cookies to the DB, so we use async
michael@0 2557 // batching with storage to make this super fast.
michael@0 2558 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
michael@0 2559 if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
michael@0 2560 mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
michael@0 2561 }
michael@0 2562
michael@0 2563 while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
michael@0 2564 if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
michael@0 2565 isHttpOnly = true;
michael@0 2566 hostIndex = sizeof(kHttpOnlyPrefix) - 1;
michael@0 2567 } else if (buffer.IsEmpty() || buffer.First() == '#') {
michael@0 2568 continue;
michael@0 2569 } else {
michael@0 2570 isHttpOnly = false;
michael@0 2571 hostIndex = 0;
michael@0 2572 }
michael@0 2573
michael@0 2574 // this is a cheap, cheesy way of parsing a tab-delimited line into
michael@0 2575 // string indexes, which can be lopped off into substrings. just for
michael@0 2576 // purposes of obfuscation, it also checks that each token was found.
michael@0 2577 // todo: use iterators?
michael@0 2578 if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
michael@0 2579 (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
michael@0 2580 (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
michael@0 2581 (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
michael@0 2582 (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
michael@0 2583 (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
michael@0 2584 continue;
michael@0 2585 }
michael@0 2586
michael@0 2587 // check the expirytime first - if it's expired, ignore
michael@0 2588 // nullstomp the trailing tab, to avoid copying the string
michael@0 2589 buffer.BeginWriting(iter);
michael@0 2590 *(iter += nameIndex - 1) = char(0);
michael@0 2591 numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
michael@0 2592 if (numInts != 1 || expires < currentTime) {
michael@0 2593 continue;
michael@0 2594 }
michael@0 2595
michael@0 2596 isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
michael@0 2597 const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
michael@0 2598 // check for bad legacy cookies (domain not starting with a dot, or containing a port),
michael@0 2599 // and discard
michael@0 2600 if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
michael@0 2601 host.FindChar(':') != kNotFound) {
michael@0 2602 continue;
michael@0 2603 }
michael@0 2604
michael@0 2605 // compute the baseDomain from the host
michael@0 2606 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 2607 if (NS_FAILED(rv))
michael@0 2608 continue;
michael@0 2609
michael@0 2610 // pre-existing cookies have appId=0, inBrowser=false
michael@0 2611 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
michael@0 2612
michael@0 2613 // Create a new nsCookie and assign the data. We don't know the cookie
michael@0 2614 // creation time, so just use the current time to generate a unique one.
michael@0 2615 nsRefPtr<nsCookie> newCookie =
michael@0 2616 nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
michael@0 2617 Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
michael@0 2618 host,
michael@4 2619 baseDomain,
michael@0 2620 Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
michael@0 2621 expires,
michael@0 2622 lastAccessedCounter,
michael@0 2623 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
michael@0 2624 false,
michael@0 2625 Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
michael@0 2626 isHttpOnly);
michael@0 2627 if (!newCookie) {
michael@0 2628 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2629 }
michael@0 2630
michael@0 2631 // trick: preserve the most-recently-used cookie ordering,
michael@0 2632 // by successively decrementing the lastAccessed time
michael@0 2633 lastAccessedCounter--;
michael@0 2634
michael@0 2635 if (originalCookieCount == 0) {
michael@0 2636 AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
michael@0 2637 }
michael@0 2638 else {
michael@0 2639 AddInternal(key, newCookie, currentTimeInUsec,
michael@0 2640 nullptr, nullptr, true);
michael@0 2641 }
michael@0 2642 }
michael@0 2643
michael@0 2644 // If we need to write to disk, do so now.
michael@0 2645 if (paramsArray) {
michael@0 2646 uint32_t length;
michael@0 2647 paramsArray->GetLength(&length);
michael@0 2648 if (length) {
michael@0 2649 rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
michael@0 2650 NS_ASSERT_SUCCESS(rv);
michael@0 2651 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 2652 rv = mDefaultDBState->stmtInsert->ExecuteAsync(
michael@0 2653 mDefaultDBState->insertListener, getter_AddRefs(handle));
michael@0 2654 NS_ASSERT_SUCCESS(rv);
michael@0 2655 }
michael@0 2656 }
michael@0 2657
michael@0 2658
michael@0 2659 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
michael@0 2660 mDefaultDBState->cookieCount));
michael@0 2661
michael@0 2662 return NS_OK;
michael@0 2663 }
michael@0 2664
michael@0 2665 /******************************************************************************
michael@0 2666 * nsCookieService impl:
michael@0 2667 * private GetCookie/SetCookie helpers
michael@0 2668 ******************************************************************************/
michael@0 2669
michael@0 2670 // helper function for GetCookieList
michael@0 2671 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
michael@0 2672
michael@0 2673 // Comparator class for sorting cookies before sending to a server.
michael@0 2674 class CompareCookiesForSending
michael@0 2675 {
michael@0 2676 public:
michael@0 2677 bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
michael@0 2678 {
michael@0 2679 return aCookie1->CreationTime() == aCookie2->CreationTime() &&
michael@0 2680 aCookie2->Path().Length() == aCookie1->Path().Length();
michael@0 2681 }
michael@0 2682
michael@0 2683 bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
michael@0 2684 {
michael@0 2685 // compare by cookie path length in accordance with RFC2109
michael@0 2686 int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
michael@0 2687 if (result != 0)
michael@0 2688 return result < 0;
michael@0 2689
michael@0 2690 // when path lengths match, older cookies should be listed first. this is
michael@0 2691 // required for backwards compatibility since some websites erroneously
michael@0 2692 // depend on receiving cookies in the order in which they were sent to the
michael@0 2693 // browser! see bug 236772.
michael@0 2694 return aCookie1->CreationTime() < aCookie2->CreationTime();
michael@0 2695 }
michael@0 2696 };
michael@0 2697
michael@0 2698 void
michael@0 2699 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
michael@0 2700 bool aIsForeign,
michael@0 2701 bool aHttpBound,
michael@0 2702 uint32_t aAppId,
michael@0 2703 bool aInBrowserElement,
michael@0 2704 bool aIsPrivate,
michael@4 2705 nsCString &aOrigDomain,
michael@0 2706 nsCString &aCookieString)
michael@0 2707 {
michael@0 2708 NS_ASSERTION(aHostURI, "null host!");
michael@0 2709
michael@0 2710 if (!mDBState) {
michael@0 2711 NS_WARNING("No DBState! Profile already closed?");
michael@0 2712 return;
michael@0 2713 }
michael@0 2714
michael@0 2715 AutoRestore<DBState*> savePrevDBState(mDBState);
michael@0 2716 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
michael@0 2717
michael@0 2718 // get the base domain, host, and path from the URI.
michael@0 2719 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
michael@0 2720 // file:// URI's (i.e. with an empty host) are allowed, but any other
michael@0 2721 // scheme must have a non-empty host. A trailing dot in the host
michael@0 2722 // is acceptable.
michael@0 2723 bool requireHostMatch;
michael@0 2724 nsAutoCString baseDomain, hostFromURI, pathFromURI;
michael@0 2725 nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
michael@0 2726 if (NS_SUCCEEDED(rv))
michael@0 2727 rv = aHostURI->GetAsciiHost(hostFromURI);
michael@0 2728 if (NS_SUCCEEDED(rv))
michael@0 2729 rv = aHostURI->GetPath(pathFromURI);
michael@0 2730 if (NS_FAILED(rv)) {
michael@0 2731 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
michael@0 2732 return;
michael@0 2733 }
michael@0 2734
michael@0 2735 // check default prefs
michael@0 2736 CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch,
michael@0 2737 nullptr);
michael@0 2738 // for GetCookie(), we don't fire rejection notifications.
michael@0 2739 switch (cookieStatus) {
michael@0 2740 case STATUS_REJECTED:
michael@0 2741 case STATUS_REJECTED_WITH_ERROR:
michael@0 2742 return;
michael@0 2743 default:
michael@0 2744 break;
michael@0 2745 }
michael@0 2746
michael@0 2747 // check if aHostURI is using an https secure protocol.
michael@0 2748 // if it isn't, then we can't send a secure cookie over the connection.
michael@0 2749 // if SchemeIs fails, assume an insecure connection, to be on the safe side
michael@0 2750 bool isSecure;
michael@0 2751 if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
michael@0 2752 isSecure = false;
michael@0 2753 }
michael@0 2754
michael@0 2755 nsCookie *cookie;
michael@0 2756 nsAutoTArray<nsCookie*, 8> foundCookieList;
michael@0 2757 int64_t currentTimeInUsec = PR_Now();
michael@0 2758 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
michael@0 2759 bool stale = false;
michael@0 2760
michael@0 2761 nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
michael@0 2762 EnsureReadDomain(key);
michael@0 2763
michael@0 2764 // perform the hash lookup
michael@0 2765 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
michael@0 2766 if (!entry)
michael@0 2767 return;
michael@0 2768
michael@0 2769 // iterate the cookies!
michael@0 2770 const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
michael@0 2771 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 2772 cookie = cookies[i];
michael@0 2773
michael@7 2774 // Check the origin key. We only continue if the saved
michael@7 2775 // origin matches matches the origin domain and a populated
michael@7 2776 // 'aOrigDomain' indicates that first party isolation is active
michael@4 2777 // FIXME:MSvB, other places iterate cookies too, handle them likewise?
michael@7 2778 if (!aOrigDomain.IsEmpty() && cookie->Origin() != aOrigDomain) {
michael@4 2779 continue;
michael@4 2780 }
michael@4 2781
michael@0 2782 // check the host, since the base domain lookup is conservative.
michael@0 2783 // first, check for an exact host or domain cookie match, e.g. "google.com"
michael@0 2784 // or ".google.com"; second a subdomain match, e.g.
michael@0 2785 // host = "mail.google.com", cookie domain = ".google.com".
michael@0 2786 if (cookie->RawHost() != hostFromURI &&
michael@0 2787 !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host())))
michael@0 2788 continue;
michael@0 2789
michael@0 2790 // if the cookie is secure and the host scheme isn't, we can't send it
michael@0 2791 if (cookie->IsSecure() && !isSecure)
michael@0 2792 continue;
michael@0 2793
michael@0 2794 // if the cookie is httpOnly and it's not going directly to the HTTP
michael@0 2795 // connection, don't send it
michael@0 2796 if (cookie->IsHttpOnly() && !aHttpBound)
michael@0 2797 continue;
michael@0 2798
michael@0 2799 // calculate cookie path length, excluding trailing '/'
michael@0 2800 uint32_t cookiePathLen = cookie->Path().Length();
michael@0 2801 if (cookiePathLen > 0 && cookie->Path().Last() == '/')
michael@0 2802 --cookiePathLen;
michael@0 2803
michael@0 2804 // if the nsIURI path is shorter than the cookie path, don't send it back
michael@0 2805 if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen)))
michael@0 2806 continue;
michael@0 2807
michael@0 2808 if (pathFromURI.Length() > cookiePathLen &&
michael@0 2809 !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) {
michael@0 2810 /*
michael@0 2811 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
michael@0 2812 * '/' is the "standard" case; the '?' test allows a site at host/abc?def
michael@0 2813 * to receive a cookie that has a path attribute of abc. this seems
michael@0 2814 * strange but at least one major site (citibank, bug 156725) depends
michael@0 2815 * on it. The test for # and ; are put in to proactively avoid problems
michael@0 2816 * with other sites - these are the only other chars allowed in the path.
michael@0 2817 */
michael@0 2818 continue;
michael@0 2819 }
michael@0 2820
michael@0 2821 // check if the cookie has expired
michael@0 2822 if (cookie->Expiry() <= currentTime) {
michael@0 2823 continue;
michael@0 2824 }
michael@0 2825
michael@0 2826 // all checks passed - add to list and check if lastAccessed stamp needs updating
michael@0 2827 foundCookieList.AppendElement(cookie);
michael@0 2828 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
michael@0 2829 stale = true;
michael@0 2830 }
michael@0 2831
michael@0 2832 int32_t count = foundCookieList.Length();
michael@0 2833 if (count == 0)
michael@0 2834 return;
michael@0 2835
michael@0 2836 // update lastAccessed timestamps. we only do this if the timestamp is stale
michael@0 2837 // by a certain amount, to avoid thrashing the db during pageload.
michael@0 2838 if (stale) {
michael@0 2839 // Create an array of parameters to bind to our update statement. Batching
michael@0 2840 // is OK here since we're updating cookies with no interleaved operations.
michael@0 2841 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
michael@0 2842 mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
michael@0 2843 if (mDBState->dbConn) {
michael@0 2844 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
michael@0 2845 }
michael@0 2846
michael@0 2847 for (int32_t i = 0; i < count; ++i) {
michael@0 2848 cookie = foundCookieList.ElementAt(i);
michael@0 2849
michael@0 2850 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
michael@0 2851 UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
michael@0 2852 }
michael@0 2853 // Update the database now if necessary.
michael@0 2854 if (paramsArray) {
michael@0 2855 uint32_t length;
michael@0 2856 paramsArray->GetLength(&length);
michael@0 2857 if (length) {
michael@0 2858 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
michael@0 2859 NS_ASSERT_SUCCESS(rv);
michael@0 2860 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 2861 rv = stmt->ExecuteAsync(mDBState->updateListener,
michael@0 2862 getter_AddRefs(handle));
michael@0 2863 NS_ASSERT_SUCCESS(rv);
michael@0 2864 }
michael@0 2865 }
michael@0 2866 }
michael@0 2867
michael@0 2868 // return cookies in order of path length; longest to shortest.
michael@0 2869 // this is required per RFC2109. if cookies match in length,
michael@0 2870 // then sort by creation time (see bug 236772).
michael@0 2871 foundCookieList.Sort(CompareCookiesForSending());
michael@0 2872
michael@0 2873 for (int32_t i = 0; i < count; ++i) {
michael@0 2874 cookie = foundCookieList.ElementAt(i);
michael@0 2875
michael@0 2876 // check if we have anything to write
michael@0 2877 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
michael@0 2878 // if we've already added a cookie to the return list, append a "; " so
michael@0 2879 // that subsequent cookies are delimited in the final list.
michael@0 2880 if (!aCookieString.IsEmpty()) {
michael@0 2881 aCookieString.AppendLiteral("; ");
michael@0 2882 }
michael@0 2883
michael@0 2884 if (!cookie->Name().IsEmpty()) {
michael@0 2885 // we have a name and value - write both
michael@0 2886 aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
michael@0 2887 } else {
michael@0 2888 // just write value
michael@0 2889 aCookieString += cookie->Value();
michael@0 2890 }
michael@0 2891 }
michael@0 2892 }
michael@0 2893
michael@0 2894 if (!aCookieString.IsEmpty())
michael@0 2895 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
michael@0 2896 }
michael@0 2897
michael@0 2898 // processes a single cookie, and returns true if there are more cookies
michael@0 2899 // to be processed
michael@0 2900 bool
michael@0 2901 nsCookieService::SetCookieInternal(nsIURI *aHostURI,
michael@0 2902 const nsCookieKey &aKey,
michael@0 2903 bool aRequireHostMatch,
michael@4 2904 const nsCString &aOrigin,
michael@0 2905 CookieStatus aStatus,
michael@0 2906 nsDependentCString &aCookieHeader,
michael@0 2907 int64_t aServerTime,
michael@0 2908 bool aFromHttp,
michael@0 2909 nsIChannel *aChannel)
michael@0 2910 {
michael@0 2911 NS_ASSERTION(aHostURI, "null host!");
michael@0 2912
michael@0 2913 // create a stack-based nsCookieAttributes, to store all the
michael@0 2914 // attributes parsed from the cookie
michael@0 2915 nsCookieAttributes cookieAttributes;
michael@0 2916
michael@0 2917 // init expiryTime such that session cookies won't prematurely expire
michael@0 2918 cookieAttributes.expiryTime = INT64_MAX;
michael@0 2919
michael@0 2920 // aCookieHeader is an in/out param to point to the next cookie, if
michael@0 2921 // there is one. Save the present value for logging purposes
michael@0 2922 nsDependentCString savedCookieHeader(aCookieHeader);
michael@0 2923
michael@0 2924 // newCookie says whether there are multiple cookies in the header;
michael@0 2925 // so we can handle them separately.
michael@0 2926 bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
michael@0 2927
michael@0 2928 int64_t currentTimeInUsec = PR_Now();
michael@0 2929
michael@0 2930 // calculate expiry time of cookie.
michael@0 2931 cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
michael@0 2932 currentTimeInUsec / PR_USEC_PER_SEC);
michael@0 2933 if (aStatus == STATUS_ACCEPT_SESSION) {
michael@0 2934 // force lifetime to session. note that the expiration time, if set above,
michael@0 2935 // will still apply.
michael@0 2936 cookieAttributes.isSession = true;
michael@0 2937 }
michael@0 2938
michael@0 2939 // reject cookie if it's over the size limit, per RFC2109
michael@0 2940 if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
michael@0 2941 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
michael@0 2942 return newCookie;
michael@0 2943 }
michael@0 2944
michael@0 2945 if (cookieAttributes.name.FindChar('\t') != kNotFound) {
michael@0 2946 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
michael@0 2947 return newCookie;
michael@0 2948 }
michael@0 2949
michael@0 2950 // domain & path checks
michael@0 2951 if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
michael@0 2952 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
michael@0 2953 return newCookie;
michael@0 2954 }
michael@0 2955 if (!CheckPath(cookieAttributes, aHostURI)) {
michael@0 2956 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
michael@0 2957 return newCookie;
michael@0 2958 }
michael@0 2959
michael@0 2960 // create a new nsCookie and copy attributes
michael@4 2961 //FIXME:MSvB, The name and value vars are neither host nor key
michael@4 2962 //FIXME:MSvB, host shows up in cookie inspector, as a index key
michael@0 2963 nsRefPtr<nsCookie> cookie =
michael@0 2964 nsCookie::Create(cookieAttributes.name,
michael@0 2965 cookieAttributes.value,
michael@0 2966 cookieAttributes.host,
michael@4 2967 aOrigin,
michael@0 2968 cookieAttributes.path,
michael@0 2969 cookieAttributes.expiryTime,
michael@0 2970 currentTimeInUsec,
michael@0 2971 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
michael@0 2972 cookieAttributes.isSession,
michael@0 2973 cookieAttributes.isSecure,
michael@0 2974 cookieAttributes.isHttpOnly);
michael@0 2975 if (!cookie)
michael@0 2976 return newCookie;
michael@0 2977
michael@0 2978 // check permissions from site permission list, or ask the user,
michael@0 2979 // to determine if we can set the cookie
michael@0 2980 if (mPermissionService) {
michael@0 2981 bool permission;
michael@0 2982 mPermissionService->CanSetCookie(aHostURI,
michael@0 2983 aChannel,
michael@0 2984 static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
michael@0 2985 &cookieAttributes.isSession,
michael@0 2986 &cookieAttributes.expiryTime,
michael@0 2987 &permission);
michael@0 2988 if (!permission) {
michael@0 2989 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
michael@0 2990 NotifyRejected(aHostURI);
michael@0 2991 return newCookie;
michael@0 2992 }
michael@0 2993
michael@0 2994 // update isSession and expiry attributes, in case they changed
michael@0 2995 cookie->SetIsSession(cookieAttributes.isSession);
michael@0 2996 cookie->SetExpiry(cookieAttributes.expiryTime);
michael@0 2997 }
michael@0 2998
michael@0 2999 // add the cookie to the list. AddInternal() takes care of logging.
michael@0 3000 // we get the current time again here, since it may have changed during prompting
michael@0 3001 AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
michael@0 3002 aFromHttp);
michael@0 3003 return newCookie;
michael@0 3004 }
michael@0 3005
michael@0 3006 // this is a backend function for adding a cookie to the list, via SetCookie.
michael@0 3007 // also used in the cookie manager, for profile migration from IE.
michael@0 3008 // it either replaces an existing cookie; or adds the cookie to the hashtable,
michael@0 3009 // and deletes a cookie (if maximum number of cookies has been
michael@0 3010 // reached). also performs list maintenance by removing expired cookies.
michael@0 3011 void
michael@0 3012 nsCookieService::AddInternal(const nsCookieKey &aKey,
michael@0 3013 nsCookie *aCookie,
michael@0 3014 int64_t aCurrentTimeInUsec,
michael@0 3015 nsIURI *aHostURI,
michael@0 3016 const char *aCookieHeader,
michael@0 3017 bool aFromHttp)
michael@0 3018 {
michael@0 3019 int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
michael@0 3020
michael@0 3021 // if the new cookie is httponly, make sure we're not coming from script
michael@0 3022 if (!aFromHttp && aCookie->IsHttpOnly()) {
michael@0 3023 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3024 "cookie is httponly; coming from script");
michael@0 3025 return;
michael@0 3026 }
michael@0 3027
michael@0 3028 nsListIter matchIter;
michael@4 3029 bool foundCookie = FindCookie(aKey, aCookie->Origin(),
michael@4 3030 aCookie->Host(), aCookie->Name(), aCookie->Path(), matchIter);
michael@0 3031
michael@0 3032 nsRefPtr<nsCookie> oldCookie;
michael@0 3033 nsCOMPtr<nsIArray> purgedList;
michael@0 3034 if (foundCookie) {
michael@0 3035 oldCookie = matchIter.Cookie();
michael@0 3036
michael@0 3037 // Check if the old cookie is stale (i.e. has already expired). If so, we
michael@0 3038 // need to be careful about the semantics of removing it and adding the new
michael@0 3039 // cookie: we want the behavior wrt adding the new cookie to be the same as
michael@0 3040 // if it didn't exist, but we still want to fire a removal notification.
michael@0 3041 if (oldCookie->Expiry() <= currentTime) {
michael@0 3042 if (aCookie->Expiry() <= currentTime) {
michael@0 3043 // The new cookie has expired and the old one is stale. Nothing to do.
michael@0 3044 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3045 "cookie has already expired");
michael@0 3046 return;
michael@0 3047 }
michael@0 3048
michael@0 3049 // Remove the stale cookie. We save notification for later, once all list
michael@0 3050 // modifications are complete.
michael@0 3051 RemoveCookieFromList(matchIter);
michael@0 3052 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3053 "stale cookie was purged");
michael@0 3054 purgedList = CreatePurgeList(oldCookie);
michael@0 3055
michael@0 3056 // We've done all we need to wrt removing and notifying the stale cookie.
michael@0 3057 // From here on out, we pretend pretend it didn't exist, so that we
michael@0 3058 // preserve expected notification semantics when adding the new cookie.
michael@0 3059 foundCookie = false;
michael@0 3060
michael@0 3061 } else {
michael@0 3062 // If the old cookie is httponly, make sure we're not coming from script.
michael@0 3063 if (!aFromHttp && oldCookie->IsHttpOnly()) {
michael@0 3064 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3065 "previously stored cookie is httponly; coming from script");
michael@0 3066 return;
michael@0 3067 }
michael@0 3068
michael@0 3069 // Remove the old cookie.
michael@0 3070 RemoveCookieFromList(matchIter);
michael@0 3071
michael@0 3072 // If the new cookie has expired -- i.e. the intent was simply to delete
michael@0 3073 // the old cookie -- then we're done.
michael@0 3074 if (aCookie->Expiry() <= currentTime) {
michael@0 3075 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3076 "previously stored cookie was deleted");
michael@0 3077 NotifyChanged(oldCookie, MOZ_UTF16("deleted"));
michael@0 3078 return;
michael@0 3079 }
michael@0 3080
michael@0 3081 // Preserve creation time of cookie for ordering purposes.
michael@0 3082 aCookie->SetCreationTime(oldCookie->CreationTime());
michael@0 3083 }
michael@0 3084
michael@0 3085 } else {
michael@0 3086 // check if cookie has already expired
michael@0 3087 if (aCookie->Expiry() <= currentTime) {
michael@0 3088 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
michael@0 3089 "cookie has already expired");
michael@0 3090 return;
michael@0 3091 }
michael@0 3092
michael@0 3093 // check if we have to delete an old cookie.
michael@0 3094 nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
michael@0 3095 if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
michael@0 3096 nsListIter iter;
michael@0 3097 FindStaleCookie(entry, currentTime, iter);
michael@0 3098 oldCookie = iter.Cookie();
michael@0 3099
michael@0 3100 // remove the oldest cookie from the domain
michael@0 3101 RemoveCookieFromList(iter);
michael@0 3102 COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
michael@0 3103 purgedList = CreatePurgeList(oldCookie);
michael@0 3104
michael@0 3105 } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
michael@0 3106 int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
michael@0 3107 int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
michael@0 3108 if (maxAge >= purgeAge) {
michael@0 3109 // we're over both size and age limits by 10%; time to purge the table!
michael@0 3110 // do this by:
michael@0 3111 // 1) removing expired cookies;
michael@0 3112 // 2) evicting the balance of old cookies until we reach the size limit.
michael@0 3113 // note that the cookieOldestTime indicator can be pessimistic - if it's
michael@0 3114 // older than the actual oldest cookie, we'll just purge more eagerly.
michael@0 3115 purgedList = PurgeCookies(aCurrentTimeInUsec);
michael@0 3116 }
michael@0 3117 }
michael@0 3118 }
michael@0 3119
michael@0 3120 // Add the cookie to the db. We do not supply a params array for batching
michael@0 3121 // because this might result in removals and additions being out of order.
michael@0 3122 AddCookieToList(aKey, aCookie, mDBState, nullptr);
michael@0 3123 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
michael@0 3124
michael@0 3125 // Now that list mutations are complete, notify observers. We do it here
michael@0 3126 // because observers may themselves attempt to mutate the list.
michael@0 3127 if (purgedList) {
michael@0 3128 NotifyChanged(purgedList, MOZ_UTF16("batch-deleted"));
michael@0 3129 }
michael@0 3130
michael@0 3131 NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
michael@0 3132 : MOZ_UTF16("added"));
michael@0 3133 }
michael@0 3134
michael@0 3135 /******************************************************************************
michael@0 3136 * nsCookieService impl:
michael@0 3137 * private cookie header parsing functions
michael@0 3138 ******************************************************************************/
michael@0 3139
michael@0 3140 // The following comment block elucidates the function of ParseAttributes.
michael@0 3141 /******************************************************************************
michael@0 3142 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
michael@0 3143 ** please note: this BNF deviates from both specifications, and reflects this
michael@0 3144 ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
michael@0 3145
michael@0 3146 ** Differences from RFC2109/2616 and explanations:
michael@0 3147 1. implied *LWS
michael@0 3148 The grammar described by this specification is word-based. Except
michael@0 3149 where noted otherwise, linear white space (<LWS>) can be included
michael@0 3150 between any two adjacent words (token or quoted-string), and
michael@0 3151 between adjacent words and separators, without changing the
michael@0 3152 interpretation of a field.
michael@0 3153 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
michael@0 3154
michael@0 3155 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
michael@0 3156 common use inside values.
michael@0 3157
michael@0 3158 3. tokens and values have looser restrictions on allowed characters than
michael@0 3159 spec. This is also due to certain characters being in common use inside
michael@0 3160 values. We allow only '=' to separate token/value pairs, and ';' to
michael@0 3161 terminate tokens or values. <LWS> is allowed within tokens and values
michael@0 3162 (see bug 206022).
michael@0 3163
michael@0 3164 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
michael@0 3165 reject control chars or non-ASCII chars. This is erring on the loose
michael@0 3166 side, since there's probably no good reason to enforce this strictness.
michael@0 3167
michael@0 3168 5. cookie <NAME> is optional, where spec requires it. This is a fairly
michael@0 3169 trivial case, but allows the flexibility of setting only a cookie <VALUE>
michael@0 3170 with a blank <NAME> and is required by some sites (see bug 169091).
michael@0 3171
michael@0 3172 6. Attribute "HttpOnly", not covered in the RFCs, is supported
michael@0 3173 (see bug 178993).
michael@0 3174
michael@0 3175 ** Begin BNF:
michael@0 3176 token = 1*<any allowed-chars except separators>
michael@0 3177 value = 1*<any allowed-chars except value-sep>
michael@0 3178 separators = ";" | "="
michael@0 3179 value-sep = ";"
michael@0 3180 cookie-sep = CR | LF
michael@0 3181 allowed-chars = <any OCTET except NUL or cookie-sep>
michael@0 3182 OCTET = <any 8-bit sequence of data>
michael@0 3183 LWS = SP | HT
michael@0 3184 NUL = <US-ASCII NUL, null control character (0)>
michael@0 3185 CR = <US-ASCII CR, carriage return (13)>
michael@0 3186 LF = <US-ASCII LF, linefeed (10)>
michael@0 3187 SP = <US-ASCII SP, space (32)>
michael@0 3188 HT = <US-ASCII HT, horizontal-tab (9)>
michael@0 3189
michael@0 3190 set-cookie = "Set-Cookie:" cookies
michael@0 3191 cookies = cookie *( cookie-sep cookie )
michael@0 3192 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
michael@0 3193 NAME = token ; cookie name
michael@0 3194 VALUE = value ; cookie value
michael@0 3195 cookie-av = token ["=" value]
michael@0 3196
michael@0 3197 valid values for cookie-av (checked post-parsing) are:
michael@0 3198 cookie-av = "Path" "=" value
michael@0 3199 | "Domain" "=" value
michael@0 3200 | "Expires" "=" value
michael@0 3201 | "Max-Age" "=" value
michael@0 3202 | "Comment" "=" value
michael@0 3203 | "Version" "=" value
michael@0 3204 | "Secure"
michael@0 3205 | "HttpOnly"
michael@0 3206
michael@0 3207 ******************************************************************************/
michael@0 3208
michael@0 3209 // helper functions for GetTokenValue
michael@0 3210 static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; }
michael@0 3211 static inline bool isterminator (char c) { return c == '\n' || c == '\r'; }
michael@0 3212 static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
michael@0 3213 static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
michael@0 3214
michael@0 3215 // Parse a single token/value pair.
michael@0 3216 // Returns true if a cookie terminator is found, so caller can parse new cookie.
michael@0 3217 bool
michael@0 3218 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
michael@0 3219 nsASingleFragmentCString::const_char_iterator &aEndIter,
michael@0 3220 nsDependentCSubstring &aTokenString,
michael@0 3221 nsDependentCSubstring &aTokenValue,
michael@0 3222 bool &aEqualsFound)
michael@0 3223 {
michael@0 3224 nsASingleFragmentCString::const_char_iterator start, lastSpace;
michael@0 3225 // initialize value string to clear garbage
michael@0 3226 aTokenValue.Rebind(aIter, aIter);
michael@0 3227
michael@0 3228 // find <token>, including any <LWS> between the end-of-token and the
michael@0 3229 // token separator. we'll remove trailing <LWS> next
michael@0 3230 while (aIter != aEndIter && iswhitespace(*aIter))
michael@0 3231 ++aIter;
michael@0 3232 start = aIter;
michael@0 3233 while (aIter != aEndIter && !istokenseparator(*aIter))
michael@0 3234 ++aIter;
michael@0 3235
michael@0 3236 // remove trailing <LWS>; first check we're not at the beginning
michael@0 3237 lastSpace = aIter;
michael@0 3238 if (lastSpace != start) {
michael@0 3239 while (--lastSpace != start && iswhitespace(*lastSpace))
michael@0 3240 continue;
michael@0 3241 ++lastSpace;
michael@0 3242 }
michael@0 3243 aTokenString.Rebind(start, lastSpace);
michael@0 3244
michael@0 3245 aEqualsFound = (*aIter == '=');
michael@0 3246 if (aEqualsFound) {
michael@0 3247 // find <value>
michael@0 3248 while (++aIter != aEndIter && iswhitespace(*aIter))
michael@0 3249 continue;
michael@0 3250
michael@0 3251 start = aIter;
michael@0 3252
michael@0 3253 // process <token>
michael@0 3254 // just look for ';' to terminate ('=' allowed)
michael@0 3255 while (aIter != aEndIter && !isvalueseparator(*aIter))
michael@0 3256 ++aIter;
michael@0 3257
michael@0 3258 // remove trailing <LWS>; first check we're not at the beginning
michael@0 3259 if (aIter != start) {
michael@0 3260 lastSpace = aIter;
michael@0 3261 while (--lastSpace != start && iswhitespace(*lastSpace))
michael@0 3262 continue;
michael@0 3263 aTokenValue.Rebind(start, ++lastSpace);
michael@0 3264 }
michael@0 3265 }
michael@0 3266
michael@0 3267 // aIter is on ';', or terminator, or EOS
michael@0 3268 if (aIter != aEndIter) {
michael@0 3269 // if on terminator, increment past & return true to process new cookie
michael@0 3270 if (isterminator(*aIter)) {
michael@0 3271 ++aIter;
michael@0 3272 return true;
michael@0 3273 }
michael@0 3274 // fall-through: aIter is on ';', increment and return false
michael@0 3275 ++aIter;
michael@0 3276 }
michael@0 3277 return false;
michael@0 3278 }
michael@0 3279
michael@0 3280 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
michael@0 3281 // cookie struct here, because we don't know which one to use until we've parsed the header.
michael@0 3282 bool
michael@0 3283 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
michael@0 3284 nsCookieAttributes &aCookieAttributes)
michael@0 3285 {
michael@0 3286 static const char kPath[] = "path";
michael@0 3287 static const char kDomain[] = "domain";
michael@0 3288 static const char kExpires[] = "expires";
michael@0 3289 static const char kMaxage[] = "max-age";
michael@0 3290 static const char kSecure[] = "secure";
michael@0 3291 static const char kHttpOnly[] = "httponly";
michael@0 3292
michael@0 3293 nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
michael@0 3294 nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
michael@0 3295 aCookieHeader.BeginReading(cookieStart);
michael@0 3296 aCookieHeader.EndReading(cookieEnd);
michael@0 3297
michael@0 3298 aCookieAttributes.isSecure = false;
michael@0 3299 aCookieAttributes.isHttpOnly = false;
michael@0 3300
michael@0 3301 nsDependentCSubstring tokenString(cookieStart, cookieStart);
michael@0 3302 nsDependentCSubstring tokenValue (cookieStart, cookieStart);
michael@0 3303 bool newCookie, equalsFound;
michael@0 3304
michael@0 3305 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
michael@0 3306 // if we find multiple cookies, return for processing
michael@0 3307 // note: if there's no '=', we assume token is <VALUE>. this is required by
michael@0 3308 // some sites (see bug 169091).
michael@0 3309 // XXX fix the parser to parse according to <VALUE> grammar for this case
michael@0 3310 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
michael@0 3311 if (equalsFound) {
michael@0 3312 aCookieAttributes.name = tokenString;
michael@0 3313 aCookieAttributes.value = tokenValue;
michael@0 3314 } else {
michael@0 3315 aCookieAttributes.value = tokenString;
michael@0 3316 }
michael@0 3317
michael@0 3318 // extract remaining attributes
michael@0 3319 while (cookieStart != cookieEnd && !newCookie) {
michael@0 3320 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
michael@0 3321
michael@0 3322 if (!tokenValue.IsEmpty()) {
michael@0 3323 tokenValue.BeginReading(tempBegin);
michael@0 3324 tokenValue.EndReading(tempEnd);
michael@0 3325 }
michael@0 3326
michael@0 3327 // decide which attribute we have, and copy the string
michael@0 3328 if (tokenString.LowerCaseEqualsLiteral(kPath))
michael@0 3329 aCookieAttributes.path = tokenValue;
michael@0 3330
michael@0 3331 else if (tokenString.LowerCaseEqualsLiteral(kDomain))
michael@0 3332 aCookieAttributes.host = tokenValue;
michael@0 3333
michael@0 3334 else if (tokenString.LowerCaseEqualsLiteral(kExpires))
michael@0 3335 aCookieAttributes.expires = tokenValue;
michael@0 3336
michael@0 3337 else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
michael@0 3338 aCookieAttributes.maxage = tokenValue;
michael@0 3339
michael@0 3340 // ignore any tokenValue for isSecure; just set the boolean
michael@0 3341 else if (tokenString.LowerCaseEqualsLiteral(kSecure))
michael@0 3342 aCookieAttributes.isSecure = true;
michael@0 3343
michael@0 3344 // ignore any tokenValue for isHttpOnly (see bug 178993);
michael@0 3345 // just set the boolean
michael@0 3346 else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
michael@0 3347 aCookieAttributes.isHttpOnly = true;
michael@0 3348 }
michael@0 3349
michael@0 3350 // rebind aCookieHeader, in case we need to process another cookie
michael@0 3351 aCookieHeader.Rebind(cookieStart, cookieEnd);
michael@0 3352 return newCookie;
michael@0 3353 }
michael@0 3354
michael@0 3355 /******************************************************************************
michael@0 3356 * nsCookieService impl:
michael@0 3357 * private domain & permission compliance enforcement functions
michael@0 3358 ******************************************************************************/
michael@0 3359
michael@0 3360 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
michael@0 3361 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
michael@0 3362 // dot may be present. If aHostURI is an IP address, an alias such as
michael@0 3363 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
michael@0 3364 // be the exact host, and aRequireHostMatch will be true to indicate that
michael@0 3365 // substring matches should not be performed.
michael@0 3366 nsresult
michael@0 3367 nsCookieService::GetBaseDomain(nsIURI *aHostURI,
michael@0 3368 nsCString &aBaseDomain,
michael@0 3369 bool &aRequireHostMatch)
michael@0 3370 {
michael@0 3371 // get the base domain. this will fail if the host contains a leading dot,
michael@0 3372 // more than one trailing dot, or is otherwise malformed.
michael@0 3373 nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
michael@0 3374 aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
michael@0 3375 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
michael@0 3376 if (aRequireHostMatch) {
michael@0 3377 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
michael@0 3378 // such as 'co.uk', or the empty string. use the host as a key in such
michael@0 3379 // cases.
michael@0 3380 rv = aHostURI->GetAsciiHost(aBaseDomain);
michael@0 3381 }
michael@0 3382 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3383
michael@0 3384 // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
michael@0 3385 if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
michael@0 3386 return NS_ERROR_INVALID_ARG;
michael@0 3387
michael@0 3388 // block any URIs without a host that aren't file:// URIs.
michael@0 3389 if (aBaseDomain.IsEmpty()) {
michael@0 3390 bool isFileURI = false;
michael@0 3391 aHostURI->SchemeIs("file", &isFileURI);
michael@0 3392 if (!isFileURI)
michael@0 3393 return NS_ERROR_INVALID_ARG;
michael@0 3394 }
michael@0 3395
michael@0 3396 return NS_OK;
michael@0 3397 }
michael@0 3398
michael@0 3399 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
michael@0 3400 // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
michael@0 3401 // that aHost is already normalized, and it may contain a leading dot
michael@0 3402 // (indicating that it represents a domain). A trailing dot may be present.
michael@0 3403 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
michael@0 3404 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
michael@0 3405 // leading dot will be treated as an error.
michael@0 3406 nsresult
michael@0 3407 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
michael@0 3408 nsCString &aBaseDomain)
michael@0 3409 {
michael@0 3410 // aHost must not be the string '.'.
michael@0 3411 if (aHost.Length() == 1 && aHost.Last() == '.')
michael@0 3412 return NS_ERROR_INVALID_ARG;
michael@0 3413
michael@0 3414 // aHost may contain a leading dot; if so, strip it now.
michael@0 3415 bool domain = !aHost.IsEmpty() && aHost.First() == '.';
michael@0 3416
michael@0 3417 // get the base domain. this will fail if the host contains a leading dot,
michael@0 3418 // more than one trailing dot, or is otherwise malformed.
michael@0 3419 nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
michael@0 3420 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
michael@0 3421 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
michael@0 3422 // aHost is either an IP address, an alias such as 'localhost', an eTLD
michael@0 3423 // such as 'co.uk', or the empty string. use the host as a key in such
michael@0 3424 // cases; however, we reject any such hosts with a leading dot, since it
michael@0 3425 // doesn't make sense for them to be domain cookies.
michael@0 3426 if (domain)
michael@0 3427 return NS_ERROR_INVALID_ARG;
michael@0 3428
michael@0 3429 aBaseDomain = aHost;
michael@0 3430 return NS_OK;
michael@0 3431 }
michael@0 3432 return rv;
michael@0 3433 }
michael@0 3434
michael@0 3435 // Normalizes the given hostname, component by component. ASCII/ACE
michael@0 3436 // components are lower-cased, and UTF-8 components are normalized per
michael@0 3437 // RFC 3454 and converted to ACE.
michael@0 3438 nsresult
michael@0 3439 nsCookieService::NormalizeHost(nsCString &aHost)
michael@0 3440 {
michael@0 3441 if (!IsASCII(aHost)) {
michael@0 3442 nsAutoCString host;
michael@0 3443 nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
michael@0 3444 if (NS_FAILED(rv))
michael@0 3445 return rv;
michael@0 3446
michael@0 3447 aHost = host;
michael@0 3448 }
michael@0 3449
michael@0 3450 ToLowerCase(aHost);
michael@0 3451 return NS_OK;
michael@0 3452 }
michael@0 3453
michael@0 3454 // returns true if 'a' is equal to or a subdomain of 'b',
michael@0 3455 // assuming no leading dots are present.
michael@0 3456 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
michael@0 3457 {
michael@0 3458 if (a == b)
michael@0 3459 return true;
michael@0 3460 if (a.Length() > b.Length())
michael@0 3461 return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
michael@0 3462 return false;
michael@0 3463 }
michael@0 3464
michael@0 3465 CookieStatus
michael@0 3466 nsCookieService::CheckPrefs(nsIURI *aHostURI,
michael@0 3467 bool aIsForeign,
michael@0 3468 bool aRequireHostMatch,
michael@0 3469 const char *aCookieHeader)
michael@0 3470 {
michael@0 3471 nsresult rv;
michael@0 3472
michael@0 3473 // don't let ftp sites get/set cookies (could be a security issue)
michael@0 3474 bool ftp;
michael@0 3475 if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
michael@0 3476 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
michael@0 3477 return STATUS_REJECTED_WITH_ERROR;
michael@0 3478 }
michael@0 3479
michael@0 3480 // check the permission list first; if we find an entry, it overrides
michael@0 3481 // default prefs. see bug 184059.
michael@0 3482 if (mPermissionService) {
michael@0 3483 nsCookieAccess access;
michael@0 3484 // Not passing an nsIChannel here is probably OK; our implementation
michael@0 3485 // doesn't do anything with it anyway.
michael@0 3486 rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
michael@0 3487
michael@0 3488 // if we found an entry, use it
michael@0 3489 if (NS_SUCCEEDED(rv)) {
michael@0 3490 switch (access) {
michael@0 3491 case nsICookiePermission::ACCESS_DENY:
michael@0 3492 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
michael@0 3493 aCookieHeader, "cookies are blocked for this site");
michael@0 3494 return STATUS_REJECTED;
michael@0 3495
michael@0 3496 case nsICookiePermission::ACCESS_ALLOW:
michael@0 3497 return STATUS_ACCEPTED;
michael@0 3498
michael@0 3499 case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
michael@0 3500 if (aIsForeign) {
michael@0 3501 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
michael@0 3502 aCookieHeader, "third party cookies are blocked "
michael@0 3503 "for this site");
michael@0 3504 return STATUS_REJECTED;
michael@0 3505
michael@0 3506 }
michael@0 3507 return STATUS_ACCEPTED;
michael@0 3508
michael@0 3509 case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
michael@0 3510 if (!aIsForeign)
michael@0 3511 return STATUS_ACCEPTED;
michael@0 3512 uint32_t priorCookieCount = 0;
michael@0 3513 nsAutoCString hostFromURI;
michael@0 3514 aHostURI->GetHost(hostFromURI);
michael@0 3515 CountCookiesFromHost(hostFromURI, &priorCookieCount);
michael@0 3516 if (priorCookieCount == 0) {
michael@0 3517 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
michael@0 3518 aCookieHeader, "third party cookies are blocked "
michael@0 3519 "for this site");
michael@0 3520 return STATUS_REJECTED;
michael@0 3521 }
michael@0 3522 return STATUS_ACCEPTED;
michael@0 3523 }
michael@0 3524 }
michael@0 3525 }
michael@0 3526
michael@0 3527 // check default prefs
michael@0 3528 if (mCookieBehavior == BEHAVIOR_REJECT) {
michael@0 3529 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
michael@0 3530 return STATUS_REJECTED;
michael@0 3531 }
michael@0 3532
michael@0 3533 // check if cookie is foreign
michael@0 3534 if (aIsForeign) {
michael@0 3535 if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
michael@0 3536 return STATUS_ACCEPT_SESSION;
michael@0 3537
michael@0 3538 if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
michael@0 3539 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
michael@0 3540 return STATUS_REJECTED;
michael@0 3541 }
michael@0 3542
michael@0 3543 if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) {
michael@0 3544 uint32_t priorCookieCount = 0;
michael@0 3545 nsAutoCString hostFromURI;
michael@0 3546 aHostURI->GetHost(hostFromURI);
michael@0 3547 CountCookiesFromHost(hostFromURI, &priorCookieCount);
michael@0 3548 if (priorCookieCount == 0) {
michael@0 3549 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
michael@0 3550 return STATUS_REJECTED;
michael@0 3551 }
michael@0 3552 if (mThirdPartySession)
michael@0 3553 return STATUS_ACCEPT_SESSION;
michael@0 3554 }
michael@0 3555 }
michael@0 3556
michael@0 3557 // if nothing has complained, accept cookie
michael@0 3558 return STATUS_ACCEPTED;
michael@0 3559 }
michael@0 3560
michael@0 3561 // processes domain attribute, and returns true if host has permission to set for this domain.
michael@0 3562 bool
michael@0 3563 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
michael@0 3564 nsIURI *aHostURI,
michael@0 3565 const nsCString &aBaseDomain,
michael@0 3566 bool aRequireHostMatch)
michael@0 3567 {
michael@0 3568 // get host from aHostURI
michael@0 3569 nsAutoCString hostFromURI;
michael@0 3570 aHostURI->GetAsciiHost(hostFromURI);
michael@0 3571
michael@0 3572 // if a domain is given, check the host has permission
michael@0 3573 if (!aCookieAttributes.host.IsEmpty()) {
michael@0 3574 // Tolerate leading '.' characters, but not if it's otherwise an empty host.
michael@0 3575 if (aCookieAttributes.host.Length() > 1 &&
michael@0 3576 aCookieAttributes.host.First() == '.') {
michael@0 3577 aCookieAttributes.host.Cut(0, 1);
michael@0 3578 }
michael@0 3579
michael@0 3580 // switch to lowercase now, to avoid case-insensitive compares everywhere
michael@0 3581 ToLowerCase(aCookieAttributes.host);
michael@0 3582
michael@0 3583 // check whether the host is either an IP address, an alias such as
michael@0 3584 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
michael@0 3585 // cases, require an exact string match for the domain, and leave the cookie
michael@0 3586 // as a non-domain one. bug 105917 originally noted the requirement to deal
michael@0 3587 // with IP addresses.
michael@0 3588 if (aRequireHostMatch)
michael@0 3589 return hostFromURI.Equals(aCookieAttributes.host);
michael@0 3590
michael@0 3591 // ensure the proposed domain is derived from the base domain; and also
michael@0 3592 // that the host domain is derived from the proposed domain (per RFC2109).
michael@0 3593 if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
michael@0 3594 IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
michael@0 3595 // prepend a dot to indicate a domain cookie
michael@0 3596 aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
michael@0 3597 return true;
michael@0 3598 }
michael@0 3599
michael@0 3600 /*
michael@0 3601 * note: RFC2109 section 4.3.2 requires that we check the following:
michael@0 3602 * that the portion of host not in domain does not contain a dot.
michael@0 3603 * this prevents hosts of the form x.y.co.nz from setting cookies in the
michael@0 3604 * entire .co.nz domain. however, it's only a only a partial solution and
michael@0 3605 * it breaks sites (IE doesn't enforce it), so we don't perform this check.
michael@0 3606 */
michael@0 3607 return false;
michael@0 3608 }
michael@0 3609
michael@0 3610 // no domain specified, use hostFromURI
michael@0 3611 aCookieAttributes.host = hostFromURI;
michael@0 3612 return true;
michael@0 3613 }
michael@0 3614
michael@0 3615 bool
michael@0 3616 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
michael@0 3617 nsIURI *aHostURI)
michael@0 3618 {
michael@0 3619 // if a path is given, check the host has permission
michael@0 3620 if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
michael@0 3621 // strip down everything after the last slash to get the path,
michael@0 3622 // ignoring slashes in the query string part.
michael@0 3623 // if we can QI to nsIURL, that'll take care of the query string portion.
michael@0 3624 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
michael@0 3625 nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
michael@0 3626 if (hostURL) {
michael@0 3627 hostURL->GetDirectory(aCookieAttributes.path);
michael@0 3628 } else {
michael@0 3629 aHostURI->GetPath(aCookieAttributes.path);
michael@0 3630 int32_t slash = aCookieAttributes.path.RFindChar('/');
michael@0 3631 if (slash != kNotFound) {
michael@0 3632 aCookieAttributes.path.Truncate(slash + 1);
michael@0 3633 }
michael@0 3634 }
michael@0 3635
michael@0 3636 #if 0
michael@0 3637 } else {
michael@0 3638 /**
michael@0 3639 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
michael@0 3640 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
michael@0 3641 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
michael@0 3642 * been disabled, unless we can evangelize these sites.
michael@0 3643 */
michael@0 3644 // get path from aHostURI
michael@0 3645 nsAutoCString pathFromURI;
michael@0 3646 if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
michael@0 3647 !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
michael@0 3648 return false;
michael@0 3649 }
michael@0 3650 #endif
michael@0 3651 }
michael@0 3652
michael@0 3653 if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
michael@0 3654 aCookieAttributes.path.FindChar('\t') != kNotFound )
michael@0 3655 return false;
michael@0 3656
michael@0 3657 return true;
michael@0 3658 }
michael@0 3659
michael@0 3660 bool
michael@0 3661 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
michael@0 3662 int64_t aServerTime,
michael@0 3663 int64_t aCurrentTime)
michael@0 3664 {
michael@0 3665 /* Determine when the cookie should expire. This is done by taking the difference between
michael@0 3666 * the server time and the time the server wants the cookie to expire, and adding that
michael@0 3667 * difference to the client time. This localizes the client time regardless of whether or
michael@0 3668 * not the TZ environment variable was set on the client.
michael@0 3669 *
michael@0 3670 * Note: We need to consider accounting for network lag here, per RFC.
michael@0 3671 */
michael@0 3672 int64_t delta;
michael@0 3673
michael@0 3674 // check for max-age attribute first; this overrides expires attribute
michael@0 3675 if (!aCookieAttributes.maxage.IsEmpty()) {
michael@0 3676 // obtain numeric value of maxageAttribute
michael@0 3677 int64_t maxage;
michael@0 3678 int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
michael@0 3679
michael@0 3680 // default to session cookie if the conversion failed
michael@0 3681 if (numInts != 1) {
michael@0 3682 return true;
michael@0 3683 }
michael@0 3684
michael@0 3685 delta = maxage;
michael@0 3686
michael@0 3687 // check for expires attribute
michael@0 3688 } else if (!aCookieAttributes.expires.IsEmpty()) {
michael@0 3689 PRTime expires;
michael@0 3690
michael@0 3691 // parse expiry time
michael@0 3692 if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
michael@0 3693 return true;
michael@0 3694 }
michael@0 3695
michael@0 3696 delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
michael@0 3697
michael@0 3698 // default to session cookie if no attributes found
michael@0 3699 } else {
michael@0 3700 return true;
michael@0 3701 }
michael@0 3702
michael@0 3703 // if this addition overflows, expiryTime will be less than currentTime
michael@0 3704 // and the cookie will be expired - that's okay.
michael@0 3705 aCookieAttributes.expiryTime = aCurrentTime + delta;
michael@0 3706
michael@0 3707 return false;
michael@0 3708 }
michael@0 3709
michael@0 3710 /******************************************************************************
michael@0 3711 * nsCookieService impl:
michael@0 3712 * private cookielist management functions
michael@0 3713 ******************************************************************************/
michael@0 3714
michael@0 3715 void
michael@0 3716 nsCookieService::RemoveAllFromMemory()
michael@0 3717 {
michael@0 3718 // clearing the hashtable will call each nsCookieEntry's dtor,
michael@0 3719 // which releases all their respective children.
michael@0 3720 mDBState->hostTable.Clear();
michael@0 3721 mDBState->cookieCount = 0;
michael@0 3722 mDBState->cookieOldestTime = INT64_MAX;
michael@0 3723 }
michael@0 3724
michael@0 3725 // stores temporary data for enumerating over the hash entries,
michael@0 3726 // since enumeration is done using callback functions
michael@0 3727 struct nsPurgeData
michael@0 3728 {
michael@0 3729 typedef nsTArray<nsListIter> ArrayType;
michael@0 3730
michael@0 3731 nsPurgeData(int64_t aCurrentTime,
michael@0 3732 int64_t aPurgeTime,
michael@0 3733 ArrayType &aPurgeList,
michael@0 3734 nsIMutableArray *aRemovedList,
michael@0 3735 mozIStorageBindingParamsArray *aParamsArray)
michael@0 3736 : currentTime(aCurrentTime)
michael@0 3737 , purgeTime(aPurgeTime)
michael@0 3738 , oldestTime(INT64_MAX)
michael@0 3739 , purgeList(aPurgeList)
michael@0 3740 , removedList(aRemovedList)
michael@0 3741 , paramsArray(aParamsArray)
michael@0 3742 {
michael@0 3743 }
michael@0 3744
michael@0 3745 // the current time, in seconds
michael@0 3746 int64_t currentTime;
michael@0 3747
michael@0 3748 // lastAccessed time older than which cookies are eligible for purge
michael@0 3749 int64_t purgeTime;
michael@0 3750
michael@0 3751 // lastAccessed time of the oldest cookie found during purge, to update our indicator
michael@0 3752 int64_t oldestTime;
michael@0 3753
michael@0 3754 // list of cookies over the age limit, for purging
michael@0 3755 ArrayType &purgeList;
michael@0 3756
michael@0 3757 // list of all cookies we've removed, for notification
michael@0 3758 nsIMutableArray *removedList;
michael@0 3759
michael@0 3760 // The array of parameters to be bound to the statement for deletion later.
michael@0 3761 mozIStorageBindingParamsArray *paramsArray;
michael@0 3762 };
michael@0 3763
michael@0 3764 // comparator class for lastaccessed times of cookies.
michael@0 3765 class CompareCookiesByAge {
michael@0 3766 public:
michael@0 3767 bool Equals(const nsListIter &a, const nsListIter &b) const
michael@0 3768 {
michael@0 3769 return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
michael@0 3770 a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
michael@0 3771 }
michael@0 3772
michael@0 3773 bool LessThan(const nsListIter &a, const nsListIter &b) const
michael@0 3774 {
michael@0 3775 // compare by lastAccessed time, and tiebreak by creationTime.
michael@0 3776 int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
michael@0 3777 if (result != 0)
michael@0 3778 return result < 0;
michael@0 3779
michael@0 3780 return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
michael@0 3781 }
michael@0 3782 };
michael@0 3783
michael@0 3784 // comparator class for sorting cookies by entry and index.
michael@0 3785 class CompareCookiesByIndex {
michael@0 3786 public:
michael@0 3787 bool Equals(const nsListIter &a, const nsListIter &b) const
michael@0 3788 {
michael@0 3789 NS_ASSERTION(a.entry != b.entry || a.index != b.index,
michael@0 3790 "cookie indexes should never be equal");
michael@0 3791 return false;
michael@0 3792 }
michael@0 3793
michael@0 3794 bool LessThan(const nsListIter &a, const nsListIter &b) const
michael@0 3795 {
michael@0 3796 // compare by entryclass pointer, then by index.
michael@0 3797 if (a.entry != b.entry)
michael@0 3798 return a.entry < b.entry;
michael@0 3799
michael@0 3800 return a.index < b.index;
michael@0 3801 }
michael@0 3802 };
michael@0 3803
michael@0 3804 PLDHashOperator
michael@0 3805 purgeCookiesCallback(nsCookieEntry *aEntry,
michael@0 3806 void *aArg)
michael@0 3807 {
michael@0 3808 nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
michael@0 3809
michael@0 3810 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
michael@0 3811 mozIStorageBindingParamsArray *array = data.paramsArray;
michael@0 3812 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
michael@0 3813 nsListIter iter(aEntry, i);
michael@0 3814 nsCookie *cookie = cookies[i];
michael@0 3815
michael@0 3816 // check if the cookie has expired
michael@0 3817 if (cookie->Expiry() <= data.currentTime) {
michael@0 3818 data.removedList->AppendElement(cookie, false);
michael@0 3819 COOKIE_LOGEVICTED(cookie, "Cookie expired");
michael@0 3820
michael@0 3821 // remove from list; do not increment our iterator
michael@0 3822 gCookieService->RemoveCookieFromList(iter, array);
michael@0 3823
michael@0 3824 } else {
michael@0 3825 // check if the cookie is over the age limit
michael@0 3826 if (cookie->LastAccessed() <= data.purgeTime) {
michael@0 3827 data.purgeList.AppendElement(iter);
michael@0 3828
michael@0 3829 } else if (cookie->LastAccessed() < data.oldestTime) {
michael@0 3830 // reset our indicator
michael@0 3831 data.oldestTime = cookie->LastAccessed();
michael@0 3832 }
michael@0 3833
michael@0 3834 ++i;
michael@0 3835 }
michael@0 3836 }
michael@0 3837 return PL_DHASH_NEXT;
michael@0 3838 }
michael@0 3839
michael@0 3840 // purges expired and old cookies in a batch operation.
michael@0 3841 already_AddRefed<nsIArray>
michael@0 3842 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
michael@0 3843 {
michael@0 3844 NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
michael@0 3845 EnsureReadComplete();
michael@0 3846
michael@0 3847 #ifdef PR_LOGGING
michael@0 3848 uint32_t initialCookieCount = mDBState->cookieCount;
michael@0 3849 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 3850 ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
michael@0 3851 mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
michael@0 3852 #endif
michael@0 3853
michael@0 3854 nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
michael@0 3855
michael@0 3856 nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
michael@0 3857
michael@0 3858 // Create a params array to batch the removals. This is OK here because
michael@0 3859 // all the removals are in order, and there are no interleaved additions.
michael@0 3860 mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
michael@0 3861 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
michael@0 3862 if (mDBState->dbConn) {
michael@0 3863 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
michael@0 3864 }
michael@0 3865
michael@0 3866 nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
michael@0 3867 aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
michael@0 3868 mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
michael@0 3869
michael@0 3870 #ifdef PR_LOGGING
michael@0 3871 uint32_t postExpiryCookieCount = mDBState->cookieCount;
michael@0 3872 #endif
michael@0 3873
michael@0 3874 // now we have a list of iterators for cookies over the age limit.
michael@0 3875 // sort them by age, and then we'll see how many to remove...
michael@0 3876 purgeList.Sort(CompareCookiesByAge());
michael@0 3877
michael@0 3878 // only remove old cookies until we reach the max cookie limit, no more.
michael@0 3879 uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
michael@0 3880 mDBState->cookieCount - mMaxNumberOfCookies : 0;
michael@0 3881 if (purgeList.Length() > excess) {
michael@0 3882 // We're not purging everything in the list, so update our indicator.
michael@0 3883 data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
michael@0 3884
michael@0 3885 purgeList.SetLength(excess);
michael@0 3886 }
michael@0 3887
michael@0 3888 // sort the list again, this time grouping cookies with a common entryclass
michael@0 3889 // together, and with ascending index. this allows us to iterate backwards
michael@0 3890 // over the list removing cookies, without having to adjust indexes as we go.
michael@0 3891 purgeList.Sort(CompareCookiesByIndex());
michael@0 3892 for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
michael@0 3893 nsCookie *cookie = purgeList[i].Cookie();
michael@0 3894 removedList->AppendElement(cookie, false);
michael@0 3895 COOKIE_LOGEVICTED(cookie, "Cookie too old");
michael@0 3896
michael@0 3897 RemoveCookieFromList(purgeList[i], paramsArray);
michael@0 3898 }
michael@0 3899
michael@0 3900 // Update the database if we have entries to purge.
michael@0 3901 if (paramsArray) {
michael@0 3902 uint32_t length;
michael@0 3903 paramsArray->GetLength(&length);
michael@0 3904 if (length) {
michael@0 3905 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
michael@0 3906 NS_ASSERT_SUCCESS(rv);
michael@0 3907 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 3908 rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
michael@0 3909 NS_ASSERT_SUCCESS(rv);
michael@0 3910 }
michael@0 3911 }
michael@0 3912
michael@0 3913 // reset the oldest time indicator
michael@0 3914 mDBState->cookieOldestTime = data.oldestTime;
michael@0 3915
michael@0 3916 COOKIE_LOGSTRING(PR_LOG_DEBUG,
michael@0 3917 ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
michael@0 3918 initialCookieCount - postExpiryCookieCount,
michael@0 3919 postExpiryCookieCount - mDBState->cookieCount,
michael@0 3920 mDBState->cookieCount,
michael@0 3921 aCurrentTimeInUsec - mDBState->cookieOldestTime));
michael@0 3922
michael@0 3923 return removedList.forget();
michael@0 3924 }
michael@0 3925
michael@0 3926 // find whether a given cookie has been previously set. this is provided by the
michael@0 3927 // nsICookieManager2 interface.
michael@0 3928 NS_IMETHODIMP
michael@0 3929 nsCookieService::CookieExists(nsICookie2 *aCookie,
michael@0 3930 bool *aFoundCookie)
michael@0 3931 {
michael@0 3932 NS_ENSURE_ARG_POINTER(aCookie);
michael@0 3933
michael@0 3934 if (!mDBState) {
michael@0 3935 NS_WARNING("No DBState! Profile already closed?");
michael@0 3936 return NS_ERROR_NOT_AVAILABLE;
michael@0 3937 }
michael@0 3938
michael@4 3939 nsAutoCString host, origin, name, path;
michael@0 3940 nsresult rv = aCookie->GetHost(host);
michael@0 3941 NS_ENSURE_SUCCESS(rv, rv);
michael@4 3942 rv = aCookie->GetOrigin(origin);
michael@4 3943 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3944 rv = aCookie->GetName(name);
michael@0 3945 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3946 rv = aCookie->GetPath(path);
michael@0 3947 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3948
michael@0 3949 nsAutoCString baseDomain;
michael@0 3950 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 3951 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3952
michael@0 3953 nsListIter iter;
michael@4 3954 *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), origin, host, name, path, iter);
michael@0 3955 return NS_OK;
michael@0 3956 }
michael@0 3957
michael@0 3958 // For a given base domain, find either an expired cookie or the oldest cookie
michael@0 3959 // by lastAccessed time.
michael@0 3960 void
michael@0 3961 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
michael@0 3962 int64_t aCurrentTime,
michael@0 3963 nsListIter &aIter)
michael@0 3964 {
michael@0 3965 aIter.entry = nullptr;
michael@0 3966
michael@0 3967 int64_t oldestTime = 0;
michael@0 3968 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
michael@0 3969 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 3970 nsCookie *cookie = cookies[i];
michael@0 3971
michael@0 3972 // If we found an expired cookie, we're done.
michael@0 3973 if (cookie->Expiry() <= aCurrentTime) {
michael@0 3974 aIter.entry = aEntry;
michael@0 3975 aIter.index = i;
michael@0 3976 return;
michael@0 3977 }
michael@0 3978
michael@0 3979 // Check if we've found the oldest cookie so far.
michael@0 3980 if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
michael@0 3981 oldestTime = cookie->LastAccessed();
michael@0 3982 aIter.entry = aEntry;
michael@0 3983 aIter.index = i;
michael@0 3984 }
michael@0 3985 }
michael@0 3986 }
michael@0 3987
michael@0 3988 // count the number of cookies stored by a particular host. this is provided by the
michael@0 3989 // nsICookieManager2 interface.
michael@0 3990 NS_IMETHODIMP
michael@0 3991 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
michael@0 3992 uint32_t *aCountFromHost)
michael@0 3993 {
michael@0 3994 if (!mDBState) {
michael@0 3995 NS_WARNING("No DBState! Profile already closed?");
michael@0 3996 return NS_ERROR_NOT_AVAILABLE;
michael@0 3997 }
michael@0 3998
michael@0 3999 // first, normalize the hostname, and fail if it contains illegal characters.
michael@0 4000 nsAutoCString host(aHost);
michael@0 4001 nsresult rv = NormalizeHost(host);
michael@0 4002 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4003
michael@0 4004 nsAutoCString baseDomain;
michael@0 4005 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 4006 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4007
michael@0 4008 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
michael@0 4009 EnsureReadDomain(key);
michael@0 4010
michael@0 4011 // Return a count of all cookies, including expired.
michael@0 4012 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
michael@0 4013 *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
michael@0 4014 return NS_OK;
michael@0 4015 }
michael@0 4016
michael@0 4017 // get an enumerator of cookies stored by a particular host. this is provided by the
michael@0 4018 // nsICookieManager2 interface.
michael@0 4019 NS_IMETHODIMP
michael@0 4020 nsCookieService::GetCookiesFromHost(const nsACString &aHost,
michael@0 4021 nsISimpleEnumerator **aEnumerator)
michael@0 4022 {
michael@0 4023 if (!mDBState) {
michael@0 4024 NS_WARNING("No DBState! Profile already closed?");
michael@0 4025 return NS_ERROR_NOT_AVAILABLE;
michael@0 4026 }
michael@0 4027
michael@0 4028 // first, normalize the hostname, and fail if it contains illegal characters.
michael@0 4029 nsAutoCString host(aHost);
michael@0 4030 nsresult rv = NormalizeHost(host);
michael@0 4031 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4032
michael@0 4033 nsAutoCString baseDomain;
michael@0 4034 rv = GetBaseDomainFromHost(host, baseDomain);
michael@0 4035 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4036
michael@0 4037 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
michael@0 4038 EnsureReadDomain(key);
michael@0 4039
michael@0 4040 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
michael@0 4041 if (!entry)
michael@0 4042 return NS_NewEmptyEnumerator(aEnumerator);
michael@0 4043
michael@0 4044 nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
michael@0 4045 const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
michael@0 4046 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 4047 cookieList.AppendObject(cookies[i]);
michael@0 4048 }
michael@0 4049
michael@0 4050 return NS_NewArrayEnumerator(aEnumerator, cookieList);
michael@0 4051 }
michael@0 4052
michael@0 4053 namespace {
michael@0 4054
michael@0 4055 /**
michael@0 4056 * This structure is used as a in/out parameter when enumerating the cookies
michael@0 4057 * for an app.
michael@0 4058 * It will contain the app id and onlyBrowserElement flag information as input
michael@0 4059 * and will contain the array of matching cookies as output.
michael@0 4060 */
michael@0 4061 struct GetCookiesForAppStruct {
michael@0 4062 uint32_t appId;
michael@0 4063 bool onlyBrowserElement;
michael@0 4064 nsCOMArray<nsICookie> cookies;
michael@0 4065
michael@0 4066 GetCookiesForAppStruct() MOZ_DELETE;
michael@0 4067 GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
michael@0 4068 : appId(aAppId)
michael@0 4069 , onlyBrowserElement(aOnlyBrowserElement)
michael@0 4070 {}
michael@0 4071 };
michael@0 4072
michael@0 4073 } // anonymous namespace
michael@0 4074
michael@0 4075 /* static */ PLDHashOperator
michael@0 4076 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
michael@0 4077 {
michael@0 4078 GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
michael@0 4079
michael@0 4080 if (entry->mAppId != data->appId ||
michael@0 4081 (data->onlyBrowserElement && !entry->mInBrowserElement)) {
michael@0 4082 return PL_DHASH_NEXT;
michael@0 4083 }
michael@0 4084
michael@0 4085 const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
michael@0 4086
michael@0 4087 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 4088 data->cookies.AppendObject(cookies[i]);
michael@0 4089 }
michael@0 4090
michael@0 4091 return PL_DHASH_NEXT;
michael@0 4092 }
michael@0 4093
michael@0 4094 NS_IMETHODIMP
michael@0 4095 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
michael@0 4096 nsISimpleEnumerator** aEnumerator)
michael@0 4097 {
michael@0 4098 if (!mDBState) {
michael@0 4099 NS_WARNING("No DBState! Profile already closed?");
michael@0 4100 return NS_ERROR_NOT_AVAILABLE;
michael@0 4101 }
michael@0 4102
michael@0 4103 NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
michael@0 4104
michael@0 4105 GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
michael@0 4106 mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
michael@0 4107
michael@0 4108 return NS_NewArrayEnumerator(aEnumerator, data.cookies);
michael@0 4109 }
michael@0 4110
michael@0 4111 NS_IMETHODIMP
michael@0 4112 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
michael@0 4113 {
michael@0 4114 nsCOMPtr<nsISimpleEnumerator> enumerator;
michael@0 4115 nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
michael@0 4116 getter_AddRefs(enumerator));
michael@0 4117
michael@0 4118 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4119
michael@0 4120 bool hasMore;
michael@0 4121 while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
michael@0 4122 nsCOMPtr<nsISupports> supports;
michael@0 4123 nsCOMPtr<nsICookie> cookie;
michael@0 4124 rv = enumerator->GetNext(getter_AddRefs(supports));
michael@0 4125 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4126
michael@0 4127 cookie = do_QueryInterface(supports);
michael@0 4128
michael@0 4129 nsAutoCString host;
michael@0 4130 cookie->GetHost(host);
michael@0 4131
michael@0 4132 nsAutoCString name;
michael@0 4133 cookie->GetName(name);
michael@0 4134
michael@0 4135 nsAutoCString path;
michael@0 4136 cookie->GetPath(path);
michael@0 4137
michael@0 4138 // nsICookie do not carry the appId/inBrowserElement information.
michael@0 4139 // That means we have to guess. This is easy for appId but not for
michael@0 4140 // inBrowserElement flag.
michael@0 4141 // A simple solution is to always ask to remove the cookie with
michael@0 4142 // inBrowserElement = true and only ask for the other one to be removed if
michael@0 4143 // we happen to be in the case of !aOnlyBrowserElement.
michael@0 4144 // Anyway, with this solution, we will likely be looking for unexistant
michael@0 4145 // cookies.
michael@0 4146 //
michael@0 4147 // NOTE: we could make this better by getting nsCookieEntry objects instead
michael@0 4148 // of plain nsICookie.
michael@0 4149 Remove(host, aAppId, true, name, path, false);
michael@0 4150 if (!aOnlyBrowserElement) {
michael@0 4151 Remove(host, aAppId, false, name, path, false);
michael@0 4152 }
michael@0 4153 }
michael@0 4154
michael@0 4155 return NS_OK;
michael@0 4156 }
michael@0 4157
michael@0 4158 // find an exact cookie specified by host, name, and path that hasn't expired.
michael@7 4159 // reveal the cookie only if its 1st party domain matches the (optional) origin.
michael@0 4160 bool
michael@0 4161 nsCookieService::FindCookie(const nsCookieKey &aKey,
michael@4 4162 const nsAFlatCString &aOrigin,
michael@0 4163 const nsAFlatCString &aHost,
michael@0 4164 const nsAFlatCString &aName,
michael@0 4165 const nsAFlatCString &aPath,
michael@0 4166 nsListIter &aIter)
michael@0 4167 {
michael@0 4168 EnsureReadDomain(aKey);
michael@0 4169
michael@0 4170 nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
michael@0 4171 if (!entry)
michael@0 4172 return false;
michael@0 4173
michael@0 4174 const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
michael@0 4175 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
michael@0 4176 nsCookie *cookie = cookies[i];
michael@0 4177
michael@7 4178 if (aHost.Equals(cookie->Host()) &&
michael@0 4179 aPath.Equals(cookie->Path()) &&
michael@0 4180 aName.Equals(cookie->Name())) {
michael@7 4181 if (aOrigin.IsEmpty() || aOrigin.Equals(cookie->Origin())) {
michael@7 4182 aIter = nsListIter(entry, i);
michael@7 4183 return true;
michael@7 4184 }
michael@0 4185 }
michael@0 4186 }
michael@0 4187
michael@0 4188 return false;
michael@0 4189 }
michael@0 4190
michael@0 4191 // remove a cookie from the hashtable, and update the iterator state.
michael@0 4192 void
michael@0 4193 nsCookieService::RemoveCookieFromList(const nsListIter &aIter,
michael@0 4194 mozIStorageBindingParamsArray *aParamsArray)
michael@0 4195 {
michael@0 4196 // if it's a non-session cookie, remove it from the db
michael@0 4197 if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
michael@0 4198 // Use the asynchronous binding methods to ensure that we do not acquire
michael@0 4199 // the database lock.
michael@0 4200 mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
michael@0 4201 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
michael@0 4202 if (!paramsArray) {
michael@0 4203 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
michael@0 4204 }
michael@0 4205
michael@0 4206 nsCOMPtr<mozIStorageBindingParams> params;
michael@0 4207 paramsArray->NewBindingParams(getter_AddRefs(params));
michael@0 4208
michael@0 4209 DebugOnly<nsresult> rv =
michael@0 4210 params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
michael@0 4211 aIter.Cookie()->Name());
michael@0 4212 NS_ASSERT_SUCCESS(rv);
michael@0 4213
michael@0 4214 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
michael@0 4215 aIter.Cookie()->Host());
michael@0 4216 NS_ASSERT_SUCCESS(rv);
michael@0 4217
michael@0 4218 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
michael@0 4219 aIter.Cookie()->Path());
michael@0 4220 NS_ASSERT_SUCCESS(rv);
michael@0 4221
michael@0 4222 rv = paramsArray->AddParams(params);
michael@0 4223 NS_ASSERT_SUCCESS(rv);
michael@0 4224
michael@0 4225 // If we weren't given a params array, we'll need to remove it ourselves.
michael@0 4226 if (!aParamsArray) {
michael@0 4227 rv = stmt->BindParameters(paramsArray);
michael@0 4228 NS_ASSERT_SUCCESS(rv);
michael@0 4229 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 4230 rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
michael@0 4231 NS_ASSERT_SUCCESS(rv);
michael@0 4232 }
michael@0 4233 }
michael@0 4234
michael@0 4235 if (aIter.entry->GetCookies().Length() == 1) {
michael@0 4236 // we're removing the last element in the array - so just remove the entry
michael@0 4237 // from the hash. note that the entryclass' dtor will take care of
michael@0 4238 // releasing this last element for us!
michael@0 4239 mDBState->hostTable.RawRemoveEntry(aIter.entry);
michael@0 4240
michael@0 4241 } else {
michael@0 4242 // just remove the element from the list
michael@0 4243 aIter.entry->GetCookies().RemoveElementAt(aIter.index);
michael@0 4244 }
michael@0 4245
michael@0 4246 --mDBState->cookieCount;
michael@0 4247 }
michael@0 4248
michael@0 4249 void
michael@0 4250 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
michael@0 4251 const nsCookieKey &aKey,
michael@0 4252 const nsCookie *aCookie)
michael@0 4253 {
michael@0 4254 NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
michael@0 4255 NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
michael@0 4256
michael@0 4257 // Use the asynchronous binding methods to ensure that we do not acquire the
michael@0 4258 // database lock.
michael@0 4259 nsCOMPtr<mozIStorageBindingParams> params;
michael@0 4260 DebugOnly<nsresult> rv =
michael@0 4261 aParamsArray->NewBindingParams(getter_AddRefs(params));
michael@0 4262 NS_ASSERT_SUCCESS(rv);
michael@0 4263
michael@0 4264 // Bind our values to params
michael@0 4265 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
michael@0 4266 aKey.mBaseDomain);
michael@0 4267 NS_ASSERT_SUCCESS(rv);
michael@0 4268
michael@0 4269 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
michael@0 4270 aKey.mAppId);
michael@0 4271 NS_ASSERT_SUCCESS(rv);
michael@0 4272
michael@0 4273 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
michael@0 4274 aKey.mInBrowserElement ? 1 : 0);
michael@0 4275 NS_ASSERT_SUCCESS(rv);
michael@0 4276
michael@0 4277 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
michael@0 4278 aCookie->Name());
michael@0 4279 NS_ASSERT_SUCCESS(rv);
michael@0 4280
michael@0 4281 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
michael@0 4282 aCookie->Value());
michael@0 4283 NS_ASSERT_SUCCESS(rv);
michael@0 4284
michael@0 4285 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
michael@0 4286 aCookie->Host());
michael@0 4287 NS_ASSERT_SUCCESS(rv);
michael@0 4288
michael@0 4289 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
michael@0 4290 aCookie->Path());
michael@0 4291 NS_ASSERT_SUCCESS(rv);
michael@0 4292
michael@0 4293 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
michael@0 4294 aCookie->Expiry());
michael@0 4295 NS_ASSERT_SUCCESS(rv);
michael@0 4296
michael@0 4297 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
michael@0 4298 aCookie->LastAccessed());
michael@0 4299 NS_ASSERT_SUCCESS(rv);
michael@0 4300
michael@0 4301 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
michael@0 4302 aCookie->CreationTime());
michael@0 4303 NS_ASSERT_SUCCESS(rv);
michael@0 4304
michael@0 4305 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
michael@0 4306 aCookie->IsSecure());
michael@0 4307 NS_ASSERT_SUCCESS(rv);
michael@0 4308
michael@0 4309 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
michael@0 4310 aCookie->IsHttpOnly());
michael@0 4311 NS_ASSERT_SUCCESS(rv);
michael@0 4312
michael@0 4313 // Bind the params to the array.
michael@0 4314 rv = aParamsArray->AddParams(params);
michael@0 4315 NS_ASSERT_SUCCESS(rv);
michael@0 4316 }
michael@0 4317
michael@0 4318 void
michael@0 4319 nsCookieService::AddCookieToList(const nsCookieKey &aKey,
michael@0 4320 nsCookie *aCookie,
michael@0 4321 DBState *aDBState,
michael@0 4322 mozIStorageBindingParamsArray *aParamsArray,
michael@0 4323 bool aWriteToDB)
michael@0 4324 {
michael@0 4325 NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
michael@0 4326 "Not writing to the DB but have a params array?");
michael@0 4327 NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
michael@0 4328 "Do not have a DB connection but have a params array?");
michael@0 4329
michael@0 4330 nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
michael@0 4331 NS_ASSERTION(entry, "can't insert element into a null entry!");
michael@0 4332
michael@0 4333 entry->GetCookies().AppendElement(aCookie);
michael@0 4334 ++aDBState->cookieCount;
michael@0 4335
michael@0 4336 // keep track of the oldest cookie, for when it comes time to purge
michael@0 4337 if (aCookie->LastAccessed() < aDBState->cookieOldestTime)
michael@0 4338 aDBState->cookieOldestTime = aCookie->LastAccessed();
michael@0 4339
michael@0 4340 // if it's a non-session cookie and hasn't just been read from the db, write it out.
michael@0 4341 if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
michael@0 4342 mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
michael@0 4343 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
michael@0 4344 if (!paramsArray) {
michael@0 4345 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
michael@0 4346 }
michael@0 4347 bindCookieParameters(paramsArray, aKey, aCookie);
michael@0 4348
michael@0 4349 // If we were supplied an array to store parameters, we shouldn't call
michael@0 4350 // executeAsync - someone up the stack will do this for us.
michael@0 4351 if (!aParamsArray) {
michael@0 4352 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
michael@0 4353 NS_ASSERT_SUCCESS(rv);
michael@0 4354 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 4355 rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
michael@0 4356 NS_ASSERT_SUCCESS(rv);
michael@0 4357 }
michael@0 4358 }
michael@0 4359 }
michael@0 4360
michael@0 4361 void
michael@0 4362 nsCookieService::UpdateCookieInList(nsCookie *aCookie,
michael@0 4363 int64_t aLastAccessed,
michael@0 4364 mozIStorageBindingParamsArray *aParamsArray)
michael@0 4365 {
michael@0 4366 NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
michael@0 4367
michael@0 4368 // udpate the lastAccessed timestamp
michael@0 4369 aCookie->SetLastAccessed(aLastAccessed);
michael@0 4370
michael@0 4371 // if it's a non-session cookie, update it in the db too
michael@0 4372 if (!aCookie->IsSession() && aParamsArray) {
michael@0 4373 // Create our params holder.
michael@0 4374 nsCOMPtr<mozIStorageBindingParams> params;
michael@0 4375 aParamsArray->NewBindingParams(getter_AddRefs(params));
michael@0 4376
michael@0 4377 // Bind our parameters.
michael@0 4378 DebugOnly<nsresult> rv =
michael@0 4379 params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
michael@0 4380 aLastAccessed);
michael@0 4381 NS_ASSERT_SUCCESS(rv);
michael@0 4382
michael@0 4383 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
michael@0 4384 aCookie->Name());
michael@0 4385 NS_ASSERT_SUCCESS(rv);
michael@0 4386
michael@0 4387 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
michael@0 4388 aCookie->Host());
michael@0 4389 NS_ASSERT_SUCCESS(rv);
michael@0 4390
michael@0 4391 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
michael@0 4392 aCookie->Path());
michael@0 4393 NS_ASSERT_SUCCESS(rv);
michael@0 4394
michael@0 4395 // Add our bound parameters to the array.
michael@0 4396 rv = aParamsArray->AddParams(params);
michael@0 4397 NS_ASSERT_SUCCESS(rv);
michael@0 4398 }
michael@0 4399 }
michael@0 4400
michael@0 4401 size_t
michael@0 4402 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
michael@0 4403 {
michael@0 4404 size_t n = aMallocSizeOf(this);
michael@0 4405
michael@0 4406 if (mDefaultDBState) {
michael@0 4407 n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
michael@0 4408 }
michael@0 4409 if (mPrivateDBState) {
michael@0 4410 n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
michael@0 4411 }
michael@0 4412
michael@0 4413 return n;
michael@0 4414 }
michael@0 4415
michael@0 4416 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
michael@0 4417
michael@0 4418 NS_IMETHODIMP
michael@0 4419 nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
michael@0 4420 nsISupports* aData)
michael@0 4421 {
michael@0 4422 return MOZ_COLLECT_REPORT(
michael@0 4423 "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
michael@0 4424 SizeOfIncludingThis(CookieServiceMallocSizeOf),
michael@0 4425 "Memory used by the cookie service.");
michael@0 4426 }

mercurial