netwerk/cookie/nsCookieService.cpp

changeset 0
6474c204b198
child 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:888625f834d3
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/. */
6
7 #include "mozilla/Attributes.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/Likely.h"
10
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
15
16 #include "mozilla/net/CookieServiceChild.h"
17 #include "mozilla/net/NeckoCommon.h"
18
19 #include "nsCookieService.h"
20 #include "nsIServiceManager.h"
21
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"
35
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"
56
57 using namespace mozilla;
58 using namespace mozilla::net;
59
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)
66
67 /******************************************************************************
68 * nsCookieService impl:
69 * useful types & constants
70 ******************************************************************************/
71
72 static nsCookieService *gCookieService;
73
74 // XXX_hack. See bug 178993.
75 // This is a hack to hide HttpOnly cookies from older browsers
76 static const char kHttpOnlyPrefix[] = "#HttpOnly_";
77
78 #define COOKIES_FILE "cookies.sqlite"
79 #define COOKIES_SCHEMA_VERSION 5
80
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
95
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
99
100 static const char kOldCookieFileName[] = "cookies.txt";
101
102 #undef LIMIT
103 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
104
105 #undef ADD_TEN_PERCENT
106 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
107
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;
114
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
121
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";
128
129 static void
130 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
131 const nsCookieKey &aKey,
132 const nsCookie *aCookie);
133
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 };
148
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 }
158
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 }
167
168 // get the nsCookie * the iterator currently points to.
169 nsCookie * Cookie() const
170 {
171 return entry->GetCookies()[index];
172 }
173
174 nsCookieEntry *entry;
175 nsCookieEntry::IndexType index;
176 };
177
178 /******************************************************************************
179 * Cookie logging handlers
180 * used for logging in nsCookieService
181 ******************************************************************************/
182
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
193
194 // define logging macros for convenience
195 #define SET_COOKIE true
196 #define GET_COOKIE false
197
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 }
207
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)
210
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
216
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
222
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;
229
230 nsAutoCString spec;
231 if (aHostURI)
232 aHostURI->GetAsciiSpec(spec);
233
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));
239
240 PRExplodedTime explodedTime;
241 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
242 char timeString[40];
243 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
244
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 }
249
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);
257
258 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("current time: %s", timeString));
259
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()));
266
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)" : ""));
272
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));
276
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 }
281
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 }
289
290 nsAutoCString spec;
291 if (aHostURI)
292 aHostURI->GetAsciiSpec(spec);
293
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"));
300
301 LogCookie(aCookie);
302
303 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
304 }
305
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));
311
312 LogCookie(aCookie);
313
314 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n"));
315 }
316
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 }
323
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 }
329
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
336
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
351
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;
363
364 public:
365 NS_IMETHOD HandleError(mozIStorageError* aError)
366 {
367 int32_t result = -1;
368 aError->GetResult(&result);
369
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
378
379 // Rebuild the database.
380 gCookieService->HandleCorruptDB(mDBState);
381
382 return NS_OK;
383 }
384 };
385
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"; }
394
395 public:
396 NS_DECL_ISUPPORTS
397
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 };
417
418 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
419
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"; }
428
429 public:
430 NS_DECL_ISUPPORTS
431
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 };
443
444 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
445
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"; }
454
455 public:
456 NS_DECL_ISUPPORTS
457
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 };
469
470 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
471
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;
481
482 public:
483 NS_DECL_ISUPPORTS
484
485 ReadCookieDBListener(DBState* dbState)
486 : DBListenerErrorHandler(dbState)
487 , mCanceled(false)
488 {
489 }
490
491 void Cancel() { mCanceled = true; }
492
493 NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
494 {
495 nsCOMPtr<mozIStorageRow> row;
496
497 while (1) {
498 DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
499 NS_ASSERT_SUCCESS(rv);
500
501 if (!row)
502 break;
503
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 }
510
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.
521
522 if (mCanceled) {
523 // We may receive a REASON_FINISHED after being canceled;
524 // tweak the reason accordingly.
525 aReason = mozIStorageStatementCallback::REASON_CANCELED;
526 }
527
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 };
548
549 NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
550
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
562
563 NS_IMETHOD Complete(nsresult, nsISupports*)
564 {
565 gCookieService->HandleDBClosed(mDBState);
566 return NS_OK;
567 }
568 };
569
570 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
571
572 namespace {
573
574 class AppClearDataObserver MOZ_FINAL : public nsIObserver {
575 public:
576 NS_DECL_ISUPPORTS
577
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));
583
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);
589
590 nsCOMPtr<nsICookieManager2> cookieManager
591 = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
592 MOZ_ASSERT(cookieManager);
593 return cookieManager->RemoveCookiesForApp(appId, browserOnly);
594 }
595 };
596
597 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
598
599 } // anonymous namespace
600
601 size_t
602 nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
603 {
604 return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
605 }
606
607 size_t
608 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
609 {
610 size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
611
612 amount += mCookies.SizeOfExcludingThis(aMallocSizeOf);
613 for (uint32_t i = 0; i < mCookies.Length(); ++i) {
614 amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
615 }
616
617 return amount;
618 }
619
620 static size_t
621 HostTableEntrySizeOfExcludingThis(nsCookieEntry *aEntry,
622 MallocSizeOf aMallocSizeOf,
623 void *arg)
624 {
625 return aEntry->SizeOfExcludingThis(aMallocSizeOf);
626 }
627
628 size_t
629 CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
630 {
631 size_t amount = 0;
632
633 amount += key.SizeOfExcludingThis(aMallocSizeOf);
634 amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
635
636 return amount;
637 }
638
639 static size_t
640 ReadSetEntrySizeOfExcludingThis(nsCookieKey *aEntry,
641 MallocSizeOf aMallocSizeOf,
642 void *)
643 {
644 return aEntry->SizeOfExcludingThis(aMallocSizeOf);
645 }
646
647 size_t
648 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
649 {
650 size_t amount = 0;
651
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);
661
662 return amount;
663 }
664
665 /******************************************************************************
666 * nsCookieService impl:
667 * singleton instance ctor/dtor methods
668 ******************************************************************************/
669
670 nsICookieService*
671 nsCookieService::GetXPCOMSingleton()
672 {
673 if (IsNeckoChild())
674 return CookieServiceChild::GetSingleton();
675
676 return GetSingleton();
677 }
678
679 nsCookieService*
680 nsCookieService::GetSingleton()
681 {
682 NS_ASSERTION(!IsNeckoChild(), "not a parent process");
683
684 if (gCookieService) {
685 NS_ADDREF(gCookieService);
686 return gCookieService;
687 }
688
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 }
702
703 return gCookieService;
704 }
705
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 }
714
715 /******************************************************************************
716 * nsCookieService impl:
717 * public methods
718 ******************************************************************************/
719
720 NS_IMPL_ISUPPORTS(nsCookieService,
721 nsICookieService,
722 nsICookieManager,
723 nsICookieManager2,
724 nsIObserver,
725 nsISupportsWeakReference,
726 nsIMemoryReporter)
727
728 nsCookieService::nsCookieService()
729 : mDBState(nullptr)
730 , mCookieBehavior(BEHAVIOR_ACCEPT)
731 , mThirdPartySession(false)
732 , mMaxNumberOfCookies(kMaxNumberOfCookies)
733 , mMaxCookiesPerHost(kMaxCookiesPerHost)
734 , mCookiePurgeAge(kCookiePurgeAge)
735 {
736 }
737
738 nsresult
739 nsCookieService::Init()
740 {
741 nsresult rv;
742 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
743 NS_ENSURE_SUCCESS(rv, rv);
744
745 mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
746 NS_ENSURE_SUCCESS(rv, rv);
747
748 mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
749 NS_ENSURE_SUCCESS(rv, rv);
750
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 }
761
762 mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
763 NS_ENSURE_SUCCESS(rv, rv);
764
765 // Init our default, and possibly private DBStates.
766 InitDBStates();
767
768 RegisterWeakMemoryReporter(this);
769
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);
775
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 }
781
782 return NS_OK;
783 }
784
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");
791
792 // Create a new default DBState and set our current one.
793 mDefaultDBState = new DBState();
794 mDBState = mDefaultDBState;
795
796 mPrivateDBState = new DBState();
797
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));
808
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 }
824
825 if (result == RESULT_FAILURE) {
826 COOKIE_LOGSTRING(PR_LOG_WARNING,
827 ("InitDBStates(): TryInitDB() failed, closing connection"));
828
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 }
835
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");
855
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 }
867
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);
873
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 }
881
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);
887
888 // Grow cookie db in 512KB increments
889 mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
890
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);
897
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);
903
904 // Start a transaction for the whole migration block.
905 mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
906
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.
922
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);
929
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);
939
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);
945
946 nsCString baseDomain, host;
947 bool hasResult;
948 while (1) {
949 rv = select->ExecuteStep(&hasResult);
950 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
951
952 if (!hasResult)
953 break;
954
955 int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
956 select->GetUTF8String(SCHEMA2_IDX_HOST, host);
957
958 rv = GetBaseDomainFromHost(host, baseDomain);
959 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
960
961 mozStorageStatementScoper scoper(update);
962
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);
969
970 rv = update->ExecuteStep(&hasResult);
971 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
972 }
973
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.
980
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.
990
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);
1004
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);
1010
1011 // Read the first row.
1012 bool hasResult;
1013 rv = select->ExecuteStep(&hasResult);
1014 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1015
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);
1022
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);
1028
1029 if (!hasResult)
1030 break;
1031
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);
1036
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);
1041
1042 rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
1043 id1);
1044 NS_ASSERT_SUCCESS(rv);
1045
1046 rv = deleteExpired->ExecuteStep(&hasResult);
1047 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1048 }
1049
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 }
1057
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);
1062
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);
1068
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.
1076
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.
1089
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);
1094
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);
1099
1100 // Create new table (with new fields and new unique constraint)
1101 rv = CreateTable();
1102 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1103
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);
1113
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);
1118
1119 COOKIE_LOGSTRING(PR_LOG_DEBUG,
1120 ("Upgraded database to schema version 5"));
1121 }
1122
1123 // No more upgrades. Update the schema version.
1124 rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1125 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1126
1127 case COOKIES_SCHEMA_VERSION:
1128 break;
1129
1130 case 0:
1131 {
1132 NS_WARNING("couldn't get schema version!");
1133
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
1143
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;
1172
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);
1177
1178 rv = CreateTable();
1179 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1180 }
1181 break;
1182 }
1183 }
1184
1185 // make operations on the table asynchronous, for performance
1186 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1187 "PRAGMA synchronous = OFF"));
1188
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"));
1195
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);
1227
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);
1233
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);
1239
1240 // if we deleted a corrupt db, don't attempt to import - return now
1241 if (aRecreateDB)
1242 return RESULT_OK;
1243
1244 // check whether to import or just read in the db
1245 if (tableExists)
1246 return Read();
1247
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;
1252
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;
1262
1263 return RESULT_OK;
1264 }
1265
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;
1274
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;
1296
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 }
1303
1304 void
1305 nsCookieService::CloseDBStates()
1306 {
1307 // Null out our private and pointer DBStates regardless.
1308 mPrivateDBState = nullptr;
1309 mDBState = nullptr;
1310
1311 // If we don't have a default DBState, we're done.
1312 if (!mDefaultDBState)
1313 return;
1314
1315 // Cleanup cached statements before we can close anything.
1316 CleanupCachedStatements();
1317
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 }
1324
1325 // Asynchronously close the connection. We will null it below.
1326 mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1327 }
1328
1329 CleanupDefaultDBConnection();
1330
1331 mDefaultDBState = nullptr;
1332 }
1333
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 }
1343
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");
1354
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;
1360
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 }
1370
1371 void
1372 nsCookieService::HandleDBClosed(DBState* aDBState)
1373 {
1374 COOKIE_LOGSTRING(PR_LOG_DEBUG,
1375 ("HandleDBClosed(): DBState %x closed", aDBState));
1376
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"));
1397
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 }
1406
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 }
1417
1418 COOKIE_LOGSTRING(PR_LOG_DEBUG,
1419 ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
1420 aDBState->corruptFlag));
1421
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;
1428
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 }
1438
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 }
1461
1462 static PLDHashOperator
1463 RebuildDBCallback(nsCookieEntry *aEntry,
1464 void *aArg)
1465 {
1466 mozIStorageBindingParamsArray* paramsArray =
1467 static_cast<mozIStorageBindingParamsArray*>(aArg);
1468
1469 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
1470 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1471 nsCookie* cookie = cookies[i];
1472
1473 if (!cookie->IsSession()) {
1474 bindCookieParameters(paramsArray, aEntry, cookie);
1475 }
1476 }
1477
1478 return PL_DHASH_NEXT;
1479 }
1480
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");
1487
1488 aDBState->corruptFlag = DBState::REBUILDING;
1489
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 }
1500
1501 COOKIE_LOGSTRING(PR_LOG_DEBUG,
1502 ("RebuildCorruptDB(): creating new database"));
1503
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 }
1518
1519 // Notify observers that we're beginning the rebuild.
1520 mObserverService->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1521
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());
1527
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 }
1537
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 }
1545
1546 nsCookieService::~nsCookieService()
1547 {
1548 CloseDBStates();
1549
1550 UnregisterWeakMemoryReporter(this);
1551
1552 gCookieService = nullptr;
1553 }
1554
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 }
1569
1570 // Close the default DB connection and null out our DBStates before
1571 // changing.
1572 CloseDBStates();
1573
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");
1577
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();
1583
1584 } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1585 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
1586 if (prefBranch)
1587 PrefChanged(prefBranch);
1588
1589 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1590 // Flush all the cookies stored by private browsing contexts
1591 mPrivateDBState = new DBState();
1592 }
1593
1594
1595 return NS_OK;
1596 }
1597
1598 NS_IMETHODIMP
1599 nsCookieService::GetCookieString(nsIURI *aHostURI,
1600 nsIChannel *aChannel,
1601 char **aCookie)
1602 {
1603 return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
1604 }
1605
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 }
1614
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);
1623
1624 // Determine whether the request is foreign. Failure is acceptable.
1625 bool isForeign = true;
1626 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
1627
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 }
1634
1635 bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
1636
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 }
1643
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 }
1664
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 }
1687
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);
1697
1698 // Determine whether the request is foreign. Failure is acceptable.
1699 bool isForeign = true;
1700 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
1701
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 }
1708
1709 bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
1710
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 }
1718
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!");
1731
1732 if (!mDBState) {
1733 NS_WARNING("No DBState! Profile already closed?");
1734 return;
1735 }
1736
1737 AutoRestore<DBState*> savePrevDBState(mDBState);
1738 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
1739
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 }
1753
1754 nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
1755
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 }
1779
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 }
1794
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 }
1803
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 }
1812
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 }
1821
1822 const char* topic;
1823
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 }
1839
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 }
1850
1851 nsAutoCString referringHost;
1852 rv = channelURI->GetHost(referringHost);
1853 if (NS_FAILED(rv)) {
1854 break;
1855 }
1856
1857 nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
1858 mObserverService->NotifyObservers(aHostURI,
1859 topic,
1860 referringHostUTF16.get());
1861 return;
1862 } while (0);
1863
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 }
1869
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 }
1887
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 }
1896
1897 /******************************************************************************
1898 * nsCookieService:
1899 * pref observer impl
1900 ******************************************************************************/
1901
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);
1908
1909 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
1910 mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
1911
1912 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
1913 mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
1914
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 }
1919
1920 bool boolval;
1921 if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
1922 mThirdPartySession = boolval;
1923 }
1924
1925 /******************************************************************************
1926 * nsICookieManager impl:
1927 * nsICookieManager
1928 ******************************************************************************/
1929
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 }
1937
1938 RemoveAllFromMemory();
1939
1940 // clear the cookie file
1941 if (mDBState->dbConn) {
1942 NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
1943
1944 // Cancel any pending read. No further results will be received by our
1945 // read listener.
1946 if (mDefaultDBState->pendingRead) {
1947 CancelAsyncRead(true);
1948 }
1949
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 }
1965
1966 NotifyChanged(nullptr, MOZ_UTF16("cleared"));
1967 return NS_OK;
1968 }
1969
1970 static PLDHashOperator
1971 COMArrayCallback(nsCookieEntry *aEntry,
1972 void *aArg)
1973 {
1974 nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
1975
1976 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
1977 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1978 data->AppendObject(cookies[i]);
1979 }
1980
1981 return PL_DHASH_NEXT;
1982 }
1983
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 }
1991
1992 EnsureReadComplete();
1993
1994 nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
1995 mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
1996
1997 return NS_NewArrayEnumerator(aEnumerator, cookieList);
1998 }
1999
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 }
2014
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);
2019
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);
2025
2026 int64_t currentTimeInUsec = PR_Now();
2027
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 }
2039
2040 AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true);
2041 return NS_OK;
2042 }
2043
2044
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 }
2054
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);
2059
2060 nsAutoCString baseDomain;
2061 rv = GetBaseDomainFromHost(host, baseDomain);
2062 NS_ENSURE_SUCCESS(rv, rv);
2063
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 }
2074
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);
2080
2081 host.Insert(NS_LITERAL_CSTRING("http://"), 0);
2082
2083 nsCOMPtr<nsIURI> uri;
2084 NS_NewURI(getter_AddRefs(uri), host);
2085
2086 if (uri)
2087 mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
2088 }
2089
2090 if (cookie) {
2091 // Everything's done. Notify observers.
2092 NotifyChanged(cookie, MOZ_UTF16("deleted"));
2093 }
2094
2095 return NS_OK;
2096 }
2097
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 }
2106
2107 /******************************************************************************
2108 * nsCookieService impl:
2109 * private file I/O functions
2110 ******************************************************************************/
2111
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);
2136
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);
2146
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);
2153
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);
2158
2159 mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
2160 rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
2161 getter_AddRefs(mDefaultDBState->pendingRead));
2162 NS_ASSERT_SUCCESS(rv);
2163
2164 nsCOMPtr<mozIStoragePendingStatement> handle;
2165 rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
2166 getter_AddRefs(handle));
2167 NS_ASSERT_SUCCESS(rv);
2168
2169 return RESULT_OK;
2170 }
2171
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);
2187
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);
2193
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 }
2203
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");
2213
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];
2219
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;
2225
2226 AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
2227 }
2228
2229 mDefaultDBState->stmtReadDomain = nullptr;
2230 mDefaultDBState->pendingRead = nullptr;
2231 mDefaultDBState->readListener = nullptr;
2232 mDefaultDBState->syncConn = nullptr;
2233 mDefaultDBState->hostArray.Clear();
2234 mDefaultDBState->readSet.Clear();
2235
2236 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
2237 mDefaultDBState->cookieCount));
2238
2239 mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr);
2240 }
2241
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");
2251
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);
2257
2258 mDefaultDBState->stmtReadDomain = nullptr;
2259 mDefaultDBState->pendingRead = nullptr;
2260 mDefaultDBState->readListener = nullptr;
2261 mDefaultDBState->hostArray.Clear();
2262
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 }
2268
2269 void
2270 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
2271 {
2272 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
2273 "not in default db state");
2274
2275 // Fast path 1: nothing to read, or we've already finished reading.
2276 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
2277 return;
2278
2279 // Fast path 2: already read in this particular domain.
2280 if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
2281 return;
2282
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));
2304
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 }
2314
2315 NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
2316
2317 mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
2318
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);
2328
2329
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 }
2343
2344 if (!hasResult)
2345 break;
2346
2347 array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain));
2348 }
2349
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 }
2355
2356 // Add it to the hashset of read entries, so we don't read it again.
2357 mDefaultDBState->readSet.PutEntry(aKey);
2358
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 }
2364
2365 void
2366 nsCookieService::EnsureReadComplete()
2367 {
2368 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
2369 "not in default db state");
2370
2371 // Fast path 1: nothing to read, or we've already finished reading.
2372 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
2373 return;
2374
2375 // Cancel the pending read, so we don't get any more results.
2376 CancelAsyncRead(false);
2377
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));
2397
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 }
2406
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 }
2421
2422 if (!hasResult)
2423 break;
2424
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;
2432
2433 CookieDomainTuple* tuple = array.AppendElement();
2434 tuple->key = key;
2435 tuple->cookie = GetCookieFromRow(stmt);
2436 }
2437
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 }
2445
2446 mDefaultDBState->syncConn = nullptr;
2447 mDefaultDBState->readSet.Clear();
2448
2449 COOKIE_LOGSTRING(PR_LOG_DEBUG,
2450 ("EnsureReadComplete(): %ld cookies read", array.Length()));
2451 }
2452
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 }
2460
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 }
2467
2468 nsresult rv;
2469 nsCOMPtr<nsIInputStream> fileInputStream;
2470 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
2471 if (NS_FAILED(rv)) return rv;
2472
2473 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
2474 if (NS_FAILED(rv)) return rv;
2475
2476 // First, ensure we've read in everything from the database, if we have one.
2477 EnsureReadComplete();
2478
2479 static const char kTrue[] = "TRUE";
2480
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;
2489
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;
2495
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 */
2508
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 */
2518
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 }
2525
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 }
2536
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 }
2549
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 }
2558
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 }
2567
2568 // compute the baseDomain from the host
2569 rv = GetBaseDomainFromHost(host, baseDomain);
2570 if (NS_FAILED(rv))
2571 continue;
2572
2573 // pre-existing cookies have appId=0, inBrowser=false
2574 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
2575
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 }
2592
2593 // trick: preserve the most-recently-used cookie ordering,
2594 // by successively decrementing the lastAccessed time
2595 lastAccessedCounter--;
2596
2597 if (originalCookieCount == 0) {
2598 AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
2599 }
2600 else {
2601 AddInternal(key, newCookie, currentTimeInUsec,
2602 nullptr, nullptr, true);
2603 }
2604 }
2605
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 }
2619
2620
2621 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported",
2622 mDefaultDBState->cookieCount));
2623
2624 return NS_OK;
2625 }
2626
2627 /******************************************************************************
2628 * nsCookieService impl:
2629 * private GetCookie/SetCookie helpers
2630 ******************************************************************************/
2631
2632 // helper function for GetCookieList
2633 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
2634
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 }
2644
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;
2651
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 };
2659
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!");
2670
2671 if (!mDBState) {
2672 NS_WARNING("No DBState! Profile already closed?");
2673 return;
2674 }
2675
2676 AutoRestore<DBState*> savePrevDBState(mDBState);
2677 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
2678
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 }
2695
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 }
2707
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 }
2715
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;
2721
2722 nsCookieKey key(baseDomain, aAppId, aInBrowserElement);
2723 EnsureReadDomain(key);
2724
2725 // perform the hash lookup
2726 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
2727 if (!entry)
2728 return;
2729
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];
2734
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;
2742
2743 // if the cookie is secure and the host scheme isn't, we can't send it
2744 if (cookie->IsSecure() && !isSecure)
2745 continue;
2746
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;
2751
2752 // calculate cookie path length, excluding trailing '/'
2753 uint32_t cookiePathLen = cookie->Path().Length();
2754 if (cookiePathLen > 0 && cookie->Path().Last() == '/')
2755 --cookiePathLen;
2756
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;
2760
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 }
2773
2774 // check if the cookie has expired
2775 if (cookie->Expiry() <= currentTime) {
2776 continue;
2777 }
2778
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 }
2784
2785 int32_t count = foundCookieList.Length();
2786 if (count == 0)
2787 return;
2788
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 }
2799
2800 for (int32_t i = 0; i < count; ++i) {
2801 cookie = foundCookieList.ElementAt(i);
2802
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 }
2820
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());
2825
2826 for (int32_t i = 0; i < count; ++i) {
2827 cookie = foundCookieList.ElementAt(i);
2828
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 }
2836
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 }
2846
2847 if (!aCookieString.IsEmpty())
2848 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
2849 }
2850
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!");
2864
2865 // create a stack-based nsCookieAttributes, to store all the
2866 // attributes parsed from the cookie
2867 nsCookieAttributes cookieAttributes;
2868
2869 // init expiryTime such that session cookies won't prematurely expire
2870 cookieAttributes.expiryTime = INT64_MAX;
2871
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);
2875
2876 // newCookie says whether there are multiple cookies in the header;
2877 // so we can handle them separately.
2878 bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
2879
2880 int64_t currentTimeInUsec = PR_Now();
2881
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 }
2890
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 }
2896
2897 if (cookieAttributes.name.FindChar('\t') != kNotFound) {
2898 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
2899 return newCookie;
2900 }
2901
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 }
2911
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;
2926
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 }
2942
2943 // update isSession and expiry attributes, in case they changed
2944 cookie->SetIsSession(cookieAttributes.isSession);
2945 cookie->SetExpiry(cookieAttributes.expiryTime);
2946 }
2947
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 }
2954
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;
2969
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 }
2976
2977 nsListIter matchIter;
2978 bool foundCookie = FindCookie(aKey, aCookie->Host(),
2979 aCookie->Name(), aCookie->Path(), matchIter);
2980
2981 nsRefPtr<nsCookie> oldCookie;
2982 nsCOMPtr<nsIArray> purgedList;
2983 if (foundCookie) {
2984 oldCookie = matchIter.Cookie();
2985
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 }
2997
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);
3004
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;
3009
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 }
3017
3018 // Remove the old cookie.
3019 RemoveCookieFromList(matchIter);
3020
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 }
3029
3030 // Preserve creation time of cookie for ordering purposes.
3031 aCookie->SetCreationTime(oldCookie->CreationTime());
3032 }
3033
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 }
3041
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();
3048
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);
3053
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 }
3068
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);
3073
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 }
3079
3080 NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed")
3081 : MOZ_UTF16("added"));
3082 }
3083
3084 /******************************************************************************
3085 * nsCookieService impl:
3086 * private cookie header parsing functions
3087 ******************************************************************************/
3088
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".
3094
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.
3103
3104 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
3105 common use inside values.
3106
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).
3112
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.
3116
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).
3120
3121 6. Attribute "HttpOnly", not covered in the RFCs, is supported
3122 (see bug 178993).
3123
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)>
3138
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]
3145
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"
3155
3156 ******************************************************************************/
3157
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 == '='; }
3163
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);
3176
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;
3184
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);
3193
3194 aEqualsFound = (*aIter == '=');
3195 if (aEqualsFound) {
3196 // find <value>
3197 while (++aIter != aEndIter && iswhitespace(*aIter))
3198 continue;
3199
3200 start = aIter;
3201
3202 // process <token>
3203 // just look for ';' to terminate ('=' allowed)
3204 while (aIter != aEndIter && !isvalueseparator(*aIter))
3205 ++aIter;
3206
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 }
3215
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 }
3228
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";
3241
3242 nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
3243 nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
3244 aCookieHeader.BeginReading(cookieStart);
3245 aCookieHeader.EndReading(cookieEnd);
3246
3247 aCookieAttributes.isSecure = false;
3248 aCookieAttributes.isHttpOnly = false;
3249
3250 nsDependentCSubstring tokenString(cookieStart, cookieStart);
3251 nsDependentCSubstring tokenValue (cookieStart, cookieStart);
3252 bool newCookie, equalsFound;
3253
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 }
3266
3267 // extract remaining attributes
3268 while (cookieStart != cookieEnd && !newCookie) {
3269 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
3270
3271 if (!tokenValue.IsEmpty()) {
3272 tokenValue.BeginReading(tempBegin);
3273 tokenValue.EndReading(tempEnd);
3274 }
3275
3276 // decide which attribute we have, and copy the string
3277 if (tokenString.LowerCaseEqualsLiteral(kPath))
3278 aCookieAttributes.path = tokenValue;
3279
3280 else if (tokenString.LowerCaseEqualsLiteral(kDomain))
3281 aCookieAttributes.host = tokenValue;
3282
3283 else if (tokenString.LowerCaseEqualsLiteral(kExpires))
3284 aCookieAttributes.expires = tokenValue;
3285
3286 else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
3287 aCookieAttributes.maxage = tokenValue;
3288
3289 // ignore any tokenValue for isSecure; just set the boolean
3290 else if (tokenString.LowerCaseEqualsLiteral(kSecure))
3291 aCookieAttributes.isSecure = true;
3292
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 }
3298
3299 // rebind aCookieHeader, in case we need to process another cookie
3300 aCookieHeader.Rebind(cookieStart, cookieEnd);
3301 return newCookie;
3302 }
3303
3304 /******************************************************************************
3305 * nsCookieService impl:
3306 * private domain & permission compliance enforcement functions
3307 ******************************************************************************/
3308
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);
3332
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;
3336
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 }
3344
3345 return NS_OK;
3346 }
3347
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;
3362
3363 // aHost may contain a leading dot; if so, strip it now.
3364 bool domain = !aHost.IsEmpty() && aHost.First() == '.';
3365
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;
3377
3378 aBaseDomain = aHost;
3379 return NS_OK;
3380 }
3381 return rv;
3382 }
3383
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;
3395
3396 aHost = host;
3397 }
3398
3399 ToLowerCase(aHost);
3400 return NS_OK;
3401 }
3402
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 }
3413
3414 CookieStatus
3415 nsCookieService::CheckPrefs(nsIURI *aHostURI,
3416 bool aIsForeign,
3417 bool aRequireHostMatch,
3418 const char *aCookieHeader)
3419 {
3420 nsresult rv;
3421
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 }
3428
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);
3436
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;
3444
3445 case nsICookiePermission::ACCESS_ALLOW:
3446 return STATUS_ACCEPTED;
3447
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;
3454
3455 }
3456 return STATUS_ACCEPTED;
3457
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 }
3475
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 }
3481
3482 // check if cookie is foreign
3483 if (aIsForeign) {
3484 if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession)
3485 return STATUS_ACCEPT_SESSION;
3486
3487 if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) {
3488 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
3489 return STATUS_REJECTED;
3490 }
3491
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 }
3505
3506 // if nothing has complained, accept cookie
3507 return STATUS_ACCEPTED;
3508 }
3509
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);
3520
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 }
3528
3529 // switch to lowercase now, to avoid case-insensitive compares everywhere
3530 ToLowerCase(aCookieAttributes.host);
3531
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);
3539
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 }
3548
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 }
3558
3559 // no domain specified, use hostFromURI
3560 aCookieAttributes.host = hostFromURI;
3561 return true;
3562 }
3563
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 }
3584
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 }
3601
3602 if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
3603 aCookieAttributes.path.FindChar('\t') != kNotFound )
3604 return false;
3605
3606 return true;
3607 }
3608
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;
3622
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);
3628
3629 // default to session cookie if the conversion failed
3630 if (numInts != 1) {
3631 return true;
3632 }
3633
3634 delta = maxage;
3635
3636 // check for expires attribute
3637 } else if (!aCookieAttributes.expires.IsEmpty()) {
3638 PRTime expires;
3639
3640 // parse expiry time
3641 if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
3642 return true;
3643 }
3644
3645 delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime;
3646
3647 // default to session cookie if no attributes found
3648 } else {
3649 return true;
3650 }
3651
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;
3655
3656 return false;
3657 }
3658
3659 /******************************************************************************
3660 * nsCookieService impl:
3661 * private cookielist management functions
3662 ******************************************************************************/
3663
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 }
3673
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;
3679
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 }
3693
3694 // the current time, in seconds
3695 int64_t currentTime;
3696
3697 // lastAccessed time older than which cookies are eligible for purge
3698 int64_t purgeTime;
3699
3700 // lastAccessed time of the oldest cookie found during purge, to update our indicator
3701 int64_t oldestTime;
3702
3703 // list of cookies over the age limit, for purging
3704 ArrayType &purgeList;
3705
3706 // list of all cookies we've removed, for notification
3707 nsIMutableArray *removedList;
3708
3709 // The array of parameters to be bound to the statement for deletion later.
3710 mozIStorageBindingParamsArray *paramsArray;
3711 };
3712
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 }
3721
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;
3728
3729 return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
3730 }
3731 };
3732
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 }
3742
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;
3748
3749 return a.index < b.index;
3750 }
3751 };
3752
3753 PLDHashOperator
3754 purgeCookiesCallback(nsCookieEntry *aEntry,
3755 void *aArg)
3756 {
3757 nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
3758
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];
3764
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");
3769
3770 // remove from list; do not increment our iterator
3771 gCookieService->RemoveCookieFromList(iter, array);
3772
3773 } else {
3774 // check if the cookie is over the age limit
3775 if (cookie->LastAccessed() <= data.purgeTime) {
3776 data.purgeList.AppendElement(iter);
3777
3778 } else if (cookie->LastAccessed() < data.oldestTime) {
3779 // reset our indicator
3780 data.oldestTime = cookie->LastAccessed();
3781 }
3782
3783 ++i;
3784 }
3785 }
3786 return PL_DHASH_NEXT;
3787 }
3788
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();
3795
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
3802
3803 nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
3804
3805 nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
3806
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 }
3814
3815 nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
3816 aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
3817 mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
3818
3819 #ifdef PR_LOGGING
3820 uint32_t postExpiryCookieCount = mDBState->cookieCount;
3821 #endif
3822
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());
3826
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();
3833
3834 purgeList.SetLength(excess);
3835 }
3836
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");
3845
3846 RemoveCookieFromList(purgeList[i], paramsArray);
3847 }
3848
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 }
3861
3862 // reset the oldest time indicator
3863 mDBState->cookieOldestTime = data.oldestTime;
3864
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));
3871
3872 return removedList.forget();
3873 }
3874
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);
3882
3883 if (!mDBState) {
3884 NS_WARNING("No DBState! Profile already closed?");
3885 return NS_ERROR_NOT_AVAILABLE;
3886 }
3887
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);
3895
3896 nsAutoCString baseDomain;
3897 rv = GetBaseDomainFromHost(host, baseDomain);
3898 NS_ENSURE_SUCCESS(rv, rv);
3899
3900 nsListIter iter;
3901 *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), host, name, path, iter);
3902 return NS_OK;
3903 }
3904
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;
3913
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];
3918
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 }
3925
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 }
3934
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 }
3945
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);
3950
3951 nsAutoCString baseDomain;
3952 rv = GetBaseDomainFromHost(host, baseDomain);
3953 NS_ENSURE_SUCCESS(rv, rv);
3954
3955 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
3956 EnsureReadDomain(key);
3957
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 }
3963
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 }
3974
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);
3979
3980 nsAutoCString baseDomain;
3981 rv = GetBaseDomainFromHost(host, baseDomain);
3982 NS_ENSURE_SUCCESS(rv, rv);
3983
3984 nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
3985 EnsureReadDomain(key);
3986
3987 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
3988 if (!entry)
3989 return NS_NewEmptyEnumerator(aEnumerator);
3990
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 }
3996
3997 return NS_NewArrayEnumerator(aEnumerator, cookieList);
3998 }
3999
4000 namespace {
4001
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;
4012
4013 GetCookiesForAppStruct() MOZ_DELETE;
4014 GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
4015 : appId(aAppId)
4016 , onlyBrowserElement(aOnlyBrowserElement)
4017 {}
4018 };
4019
4020 } // anonymous namespace
4021
4022 /* static */ PLDHashOperator
4023 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
4024 {
4025 GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
4026
4027 if (entry->mAppId != data->appId ||
4028 (data->onlyBrowserElement && !entry->mInBrowserElement)) {
4029 return PL_DHASH_NEXT;
4030 }
4031
4032 const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
4033
4034 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4035 data->cookies.AppendObject(cookies[i]);
4036 }
4037
4038 return PL_DHASH_NEXT;
4039 }
4040
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 }
4049
4050 NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
4051
4052 GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
4053 mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
4054
4055 return NS_NewArrayEnumerator(aEnumerator, data.cookies);
4056 }
4057
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));
4064
4065 NS_ENSURE_SUCCESS(rv, rv);
4066
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);
4073
4074 cookie = do_QueryInterface(supports);
4075
4076 nsAutoCString host;
4077 cookie->GetHost(host);
4078
4079 nsAutoCString name;
4080 cookie->GetName(name);
4081
4082 nsAutoCString path;
4083 cookie->GetPath(path);
4084
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 }
4101
4102 return NS_OK;
4103 }
4104
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);
4114
4115 nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
4116 if (!entry)
4117 return false;
4118
4119 const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4120 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4121 nsCookie *cookie = cookies[i];
4122
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 }
4130
4131 return false;
4132 }
4133
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 }
4148
4149 nsCOMPtr<mozIStorageBindingParams> params;
4150 paramsArray->NewBindingParams(getter_AddRefs(params));
4151
4152 DebugOnly<nsresult> rv =
4153 params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
4154 aIter.Cookie()->Name());
4155 NS_ASSERT_SUCCESS(rv);
4156
4157 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
4158 aIter.Cookie()->Host());
4159 NS_ASSERT_SUCCESS(rv);
4160
4161 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
4162 aIter.Cookie()->Path());
4163 NS_ASSERT_SUCCESS(rv);
4164
4165 rv = paramsArray->AddParams(params);
4166 NS_ASSERT_SUCCESS(rv);
4167
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 }
4177
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);
4183
4184 } else {
4185 // just remove the element from the list
4186 aIter.entry->GetCookies().RemoveElementAt(aIter.index);
4187 }
4188
4189 --mDBState->cookieCount;
4190 }
4191
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!");
4199
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);
4206
4207 // Bind our values to params
4208 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
4209 aKey.mBaseDomain);
4210 NS_ASSERT_SUCCESS(rv);
4211
4212 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"),
4213 aKey.mAppId);
4214 NS_ASSERT_SUCCESS(rv);
4215
4216 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"),
4217 aKey.mInBrowserElement ? 1 : 0);
4218 NS_ASSERT_SUCCESS(rv);
4219
4220 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
4221 aCookie->Name());
4222 NS_ASSERT_SUCCESS(rv);
4223
4224 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
4225 aCookie->Value());
4226 NS_ASSERT_SUCCESS(rv);
4227
4228 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
4229 aCookie->Host());
4230 NS_ASSERT_SUCCESS(rv);
4231
4232 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
4233 aCookie->Path());
4234 NS_ASSERT_SUCCESS(rv);
4235
4236 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
4237 aCookie->Expiry());
4238 NS_ASSERT_SUCCESS(rv);
4239
4240 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
4241 aCookie->LastAccessed());
4242 NS_ASSERT_SUCCESS(rv);
4243
4244 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
4245 aCookie->CreationTime());
4246 NS_ASSERT_SUCCESS(rv);
4247
4248 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
4249 aCookie->IsSecure());
4250 NS_ASSERT_SUCCESS(rv);
4251
4252 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
4253 aCookie->IsHttpOnly());
4254 NS_ASSERT_SUCCESS(rv);
4255
4256 // Bind the params to the array.
4257 rv = aParamsArray->AddParams(params);
4258 NS_ASSERT_SUCCESS(rv);
4259 }
4260
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?");
4272
4273 nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
4274 NS_ASSERTION(entry, "can't insert element into a null entry!");
4275
4276 entry->GetCookies().AppendElement(aCookie);
4277 ++aDBState->cookieCount;
4278
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();
4282
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);
4291
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 }
4303
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!");
4310
4311 // udpate the lastAccessed timestamp
4312 aCookie->SetLastAccessed(aLastAccessed);
4313
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));
4319
4320 // Bind our parameters.
4321 DebugOnly<nsresult> rv =
4322 params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
4323 aLastAccessed);
4324 NS_ASSERT_SUCCESS(rv);
4325
4326 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
4327 aCookie->Name());
4328 NS_ASSERT_SUCCESS(rv);
4329
4330 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
4331 aCookie->Host());
4332 NS_ASSERT_SUCCESS(rv);
4333
4334 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
4335 aCookie->Path());
4336 NS_ASSERT_SUCCESS(rv);
4337
4338 // Add our bound parameters to the array.
4339 rv = aParamsArray->AddParams(params);
4340 NS_ASSERT_SUCCESS(rv);
4341 }
4342 }
4343
4344 size_t
4345 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
4346 {
4347 size_t n = aMallocSizeOf(this);
4348
4349 if (mDefaultDBState) {
4350 n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
4351 }
4352 if (mPrivateDBState) {
4353 n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
4354 }
4355
4356 return n;
4357 }
4358
4359 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
4360
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 }

mercurial