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