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