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