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/ClearOnShutdown.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/Likely.h"
11 #include "mozilla/Printf.h"
12 #include "mozilla/Unused.h"
13 
14 #include "mozilla/net/CookieServiceChild.h"
15 #include "mozilla/net/NeckoCommon.h"
16 
17 #include "nsCookieService.h"
18 #include "nsContentUtils.h"
19 #include "nsIServiceManager.h"
20 #include "nsIScriptSecurityManager.h"
21 
22 #include "nsIIOService.h"
23 #include "nsIPermissionManager.h"
24 #include "nsIProtocolHandler.h"
25 #include "nsIPrefBranch.h"
26 #include "nsIPrefService.h"
27 #include "nsIScriptError.h"
28 #include "nsICookiePermission.h"
29 #include "nsIURI.h"
30 #include "nsIURL.h"
31 #include "nsIChannel.h"
32 #include "nsIFile.h"
33 #include "nsIObserverService.h"
34 #include "nsILineInputStream.h"
35 #include "nsIEffectiveTLDService.h"
36 #include "nsIIDNService.h"
37 #include "nsIThread.h"
38 #include "mozIThirdPartyUtil.h"
39 
40 #include "nsTArray.h"
41 #include "nsCOMArray.h"
42 #include "nsIMutableArray.h"
43 #include "nsArrayEnumerator.h"
44 #include "nsEnumeratorUtils.h"
45 #include "nsAutoPtr.h"
46 #include "nsReadableUtils.h"
47 #include "nsCRT.h"
48 #include "prprf.h"
49 #include "nsNetUtil.h"
50 #include "nsNetCID.h"
51 #include "nsISimpleEnumerator.h"
52 #include "nsIInputStream.h"
53 #include "nsAppDirectoryServiceDefs.h"
54 #include "nsNetCID.h"
55 #include "mozilla/storage.h"
56 #include "mozilla/AutoRestore.h"
57 #include "mozilla/FileUtils.h"
58 #include "mozilla/ScopeExit.h"
59 #include "mozilla/Telemetry.h"
60 #include "nsIConsoleService.h"
61 #include "nsVariant.h"
62 
63 using namespace mozilla;
64 using namespace mozilla::net;
65 
66 // Create key from baseDomain that will access the default cookie namespace.
67 // TODO: When we figure out what the API will look like for nsICookieManager{2}
68 // on content processes (see bug 777620), change to use the appropriate app
69 // namespace.  For now those IDLs aren't supported on child processes.
70 #define DEFAULT_APP_KEY(baseDomain) nsCookieKey(baseDomain, OriginAttributes())
71 
72 /******************************************************************************
73  * nsCookieService impl:
74  * useful types & constants
75  ******************************************************************************/
76 
77 static StaticRefPtr<nsCookieService> gCookieService;
78 bool nsCookieService::sSameSiteEnabled = false;
79 
80 // XXX_hack. See bug 178993.
81 // This is a hack to hide HttpOnly cookies from older browsers
82 #define HTTP_ONLY_PREFIX "#HttpOnly_"
83 
84 #define COOKIES_FILE "cookies.sqlite"
85 #define COOKIES_SCHEMA_VERSION 9
86 
87 // parameter indexes; see |Read|
88 #define IDX_NAME 0
89 #define IDX_VALUE 1
90 #define IDX_HOST 2
91 #define IDX_PATH 3
92 #define IDX_EXPIRY 4
93 #define IDX_LAST_ACCESSED 5
94 #define IDX_CREATION_TIME 6
95 #define IDX_SECURE 7
96 #define IDX_HTTPONLY 8
97 #define IDX_BASE_DOMAIN 9
98 #define IDX_ORIGIN_ATTRIBUTES 10
99 #define IDX_SAME_SITE 11
100 
101 #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
102 
103 static const int64_t kCookiePurgeAge =
104     int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC;  // 30 days in microseconds
105 
106 #define OLD_COOKIE_FILE_NAME "cookies.txt"
107 
108 #undef LIMIT
109 #define LIMIT(x, low, high, default) \
110   ((x) >= (low) && (x) <= (high) ? (x) : (default))
111 
112 #undef ADD_TEN_PERCENT
113 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i) / 10)
114 
115 // default limits for the cookie list. these can be tuned by the
116 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
117 static const uint32_t kMaxNumberOfCookies = 3000;
118 static const uint32_t kMaxCookiesPerHost = 150;
119 static const uint32_t kMaxBytesPerCookie = 4096;
120 static const uint32_t kMaxBytesPerPath = 1024;
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[] =
128     "network.cookie.thirdparty.sessionOnly";
129 static const char kPrefThirdPartyNonsecureSession[] =
130     "network.cookie.thirdparty.nonsecureSessionOnly";
131 static const char kCookieLeaveSecurityAlone[] =
132     "network.cookie.leave-secure-alone";
133 
134 // For telemetry COOKIE_LEAVE_SECURE_ALONE
135 #define BLOCKED_SECURE_SET_FROM_HTTP 0
136 #define BLOCKED_DOWNGRADE_SECURE_INEXACT 1
137 #define DOWNGRADE_SECURE_FROM_SECURE_INEXACT 2
138 #define EVICTED_NEWER_INSECURE 3
139 #define EVICTED_OLDEST_COOKIE 4
140 #define EVICTED_PREFERRED_COOKIE 5
141 #define EVICTING_SECURE_BLOCKED 6
142 #define BLOCKED_DOWNGRADE_SECURE_EXACT 7
143 #define DOWNGRADE_SECURE_FROM_SECURE_EXACT 8
144 
145 static void bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
146                                  const nsCookieKey &aKey,
147                                  const nsCookie *aCookie);
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   // default (non-initializing) constructor.
154   nsListIter() = default;
155 
156   // explicit constructor to a given iterator state with entryclass 'aEntry'
157   // and index 'aIndex'.
nsListIternsListIter158   explicit nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
159       : entry(aEntry), index(aIndex) {}
160 
161   // get the nsCookie * the iterator currently points to.
CookiensListIter162   nsCookie *Cookie() const { return entry->GetCookies()[index]; }
163 
164   nsCookieEntry *entry;
165   nsCookieEntry::IndexType index;
166 };
167 
168 /******************************************************************************
169  * Cookie logging handlers
170  * used for logging in nsCookieService
171  ******************************************************************************/
172 
173 // logging handlers
174 #ifdef MOZ_LOGGING
175 // in order to do logging, the following environment variables need to be set:
176 //
177 //    set MOZ_LOG=cookie:3 -- shows rejected cookies
178 //    set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
179 //    set MOZ_LOG_FILE=cookie.log
180 //
181 #include "mozilla/Logging.h"
182 #endif
183 
184 // define logging macros for convenience
185 #define SET_COOKIE true
186 #define GET_COOKIE false
187 
188 static LazyLogModule gCookieLog("cookie");
189 
190 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
191 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
192 
193 #define COOKIE_LOGEVICTED(a, details)                                    \
194   PR_BEGIN_MACRO                                                         \
195   if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) LogEvicted(a, details); \
196   PR_END_MACRO
197 
198 #define COOKIE_LOGSTRING(lvl, fmt)  \
199   PR_BEGIN_MACRO                    \
200   MOZ_LOG(gCookieLog, lvl, fmt);    \
201   MOZ_LOG(gCookieLog, lvl, ("\n")); \
202   PR_END_MACRO
203 
LogFailure(bool aSetCookie,nsIURI * aHostURI,const char * aCookieString,const char * aReason)204 static void LogFailure(bool aSetCookie, nsIURI *aHostURI,
205                        const char *aCookieString, const char *aReason) {
206   // if logging isn't enabled, return now to save cycles
207   if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) return;
208 
209   nsAutoCString spec;
210   if (aHostURI) aHostURI->GetAsciiSpec(spec);
211 
212   MOZ_LOG(gCookieLog, LogLevel::Warning,
213           ("===== %s =====\n",
214            aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
215   MOZ_LOG(gCookieLog, LogLevel::Warning, ("request URL: %s\n", spec.get()));
216   if (aSetCookie)
217     MOZ_LOG(gCookieLog, LogLevel::Warning,
218             ("cookie string: %s\n", aCookieString));
219 
220   PRExplodedTime explodedTime;
221   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
222   char timeString[40];
223   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
224 
225   MOZ_LOG(gCookieLog, LogLevel::Warning, ("current time: %s", timeString));
226   MOZ_LOG(gCookieLog, LogLevel::Warning, ("rejected because %s\n", aReason));
227   MOZ_LOG(gCookieLog, LogLevel::Warning, ("\n"));
228 }
229 
LogCookie(nsCookie * aCookie)230 static void LogCookie(nsCookie *aCookie) {
231   PRExplodedTime explodedTime;
232   PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
233   char timeString[40];
234   PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
235 
236   MOZ_LOG(gCookieLog, LogLevel::Debug, ("current time: %s", timeString));
237 
238   if (aCookie) {
239     MOZ_LOG(gCookieLog, LogLevel::Debug, ("----------------\n"));
240     MOZ_LOG(gCookieLog, LogLevel::Debug, ("name: %s\n", aCookie->Name().get()));
241     MOZ_LOG(gCookieLog, LogLevel::Debug,
242             ("value: %s\n", aCookie->Value().get()));
243     MOZ_LOG(gCookieLog, LogLevel::Debug,
244             ("%s: %s\n", aCookie->IsDomain() ? "domain" : "host",
245              aCookie->Host().get()));
246     MOZ_LOG(gCookieLog, LogLevel::Debug, ("path: %s\n", aCookie->Path().get()));
247 
248     PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
249                    PR_GMTParameters, &explodedTime);
250     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
251     MOZ_LOG(gCookieLog, LogLevel::Debug,
252             ("expires: %s%s", timeString,
253              aCookie->IsSession() ? " (at end of session)" : ""));
254 
255     PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
256     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
257     MOZ_LOG(gCookieLog, LogLevel::Debug, ("created: %s", timeString));
258 
259     MOZ_LOG(gCookieLog, LogLevel::Debug,
260             ("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
261     MOZ_LOG(gCookieLog, LogLevel::Debug,
262             ("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
263 
264     nsAutoCString suffix;
265     aCookie->OriginAttributesRef().CreateSuffix(suffix);
266     MOZ_LOG(gCookieLog, LogLevel::Debug,
267             ("origin attributes: %s\n",
268              suffix.IsEmpty() ? "{empty}" : suffix.get()));
269   }
270 }
271 
LogSuccess(bool aSetCookie,nsIURI * aHostURI,const char * aCookieString,nsCookie * aCookie,bool aReplacing)272 static void LogSuccess(bool aSetCookie, nsIURI *aHostURI,
273                        const char *aCookieString, nsCookie *aCookie,
274                        bool aReplacing) {
275   // if logging isn't enabled, return now to save cycles
276   if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
277     return;
278   }
279 
280   nsAutoCString spec;
281   if (aHostURI) aHostURI->GetAsciiSpec(spec);
282 
283   MOZ_LOG(gCookieLog, LogLevel::Debug,
284           ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
285   MOZ_LOG(gCookieLog, LogLevel::Debug, ("request URL: %s\n", spec.get()));
286   MOZ_LOG(gCookieLog, LogLevel::Debug, ("cookie string: %s\n", aCookieString));
287   if (aSetCookie)
288     MOZ_LOG(gCookieLog, LogLevel::Debug,
289             ("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
290 
291   LogCookie(aCookie);
292 
293   MOZ_LOG(gCookieLog, LogLevel::Debug, ("\n"));
294 }
295 
LogEvicted(nsCookie * aCookie,const char * details)296 static void LogEvicted(nsCookie *aCookie, const char *details) {
297   MOZ_LOG(gCookieLog, LogLevel::Debug, ("===== COOKIE EVICTED =====\n"));
298   MOZ_LOG(gCookieLog, LogLevel::Debug, ("%s\n", details));
299 
300   LogCookie(aCookie);
301 
302   MOZ_LOG(gCookieLog, LogLevel::Debug, ("\n"));
303 }
304 
305 // inline wrappers to make passing in nsCStrings easier
LogFailure(bool aSetCookie,nsIURI * aHostURI,const nsCString & aCookieString,const char * aReason)306 static inline void LogFailure(bool aSetCookie, nsIURI *aHostURI,
307                               const nsCString &aCookieString,
308                               const char *aReason) {
309   LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
310 }
311 
LogSuccess(bool aSetCookie,nsIURI * aHostURI,const nsCString & aCookieString,nsCookie * aCookie,bool aReplacing)312 static inline void LogSuccess(bool aSetCookie, nsIURI *aHostURI,
313                               const nsCString &aCookieString, nsCookie *aCookie,
314                               bool aReplacing) {
315   LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
316 }
317 
318 #ifdef DEBUG
319 #define NS_ASSERT_SUCCESS(res)                                       \
320   PR_BEGIN_MACRO                                                     \
321   nsresult __rv = res; /* Do not evaluate |res| more than once! */   \
322   if (NS_FAILED(__rv)) {                                             \
323     SmprintfPointer msg = mozilla::Smprintf(                         \
324         "NS_ASSERT_SUCCESS(%s) failed with result 0x%" PRIX32, #res, \
325         static_cast<uint32_t>(__rv));                                \
326     NS_ASSERTION(NS_SUCCEEDED(__rv), msg.get());                     \
327   }                                                                  \
328   PR_END_MACRO
329 #else
330 #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
331 #endif
332 
333 /******************************************************************************
334  * DBListenerErrorHandler impl:
335  * Parent class for our async storage listeners that handles the logging of
336  * errors.
337  ******************************************************************************/
338 class DBListenerErrorHandler : public mozIStorageStatementCallback {
339  protected:
DBListenerErrorHandler(DBState * dbState)340   explicit DBListenerErrorHandler(DBState *dbState) : mDBState(dbState) {}
341   RefPtr<DBState> mDBState;
342   virtual const char *GetOpType() = 0;
343 
344  public:
HandleError(mozIStorageError * aError)345   NS_IMETHOD HandleError(mozIStorageError *aError) override {
346     if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
347       int32_t result = -1;
348       aError->GetResult(&result);
349 
350       nsAutoCString message;
351       aError->GetMessage(message);
352       COOKIE_LOGSTRING(
353           LogLevel::Warning,
354           ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
355            "performing operation '%s' with message '%s'; rebuilding database.",
356            result, GetOpType(), message.get()));
357     }
358 
359     // Rebuild the database.
360     gCookieService->HandleCorruptDB(mDBState);
361 
362     return NS_OK;
363   }
364 };
365 
366 /******************************************************************************
367  * InsertCookieDBListener impl:
368  * mozIStorageStatementCallback used to track asynchronous insertion operations.
369  ******************************************************************************/
370 class InsertCookieDBListener final : public DBListenerErrorHandler {
371  private:
GetOpType()372   const char *GetOpType() override { return "INSERT"; }
373 
374   ~InsertCookieDBListener() = default;
375 
376  public:
377   NS_DECL_ISUPPORTS
378 
InsertCookieDBListener(DBState * dbState)379   explicit InsertCookieDBListener(DBState *dbState)
380       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)381   NS_IMETHOD HandleResult(mozIStorageResultSet *) override {
382     NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
383     return NS_OK;
384   }
HandleCompletion(uint16_t aReason)385   NS_IMETHOD HandleCompletion(uint16_t aReason) override {
386     // If we were rebuilding the db and we succeeded, make our corruptFlag say
387     // so.
388     if (mDBState->corruptFlag == DBState::REBUILDING &&
389         aReason == mozIStorageStatementCallback::REASON_FINISHED) {
390       COOKIE_LOGSTRING(
391           LogLevel::Debug,
392           ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
393       mDBState->corruptFlag = DBState::OK;
394     }
395     return NS_OK;
396   }
397 };
398 
399 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
400 
401 /******************************************************************************
402  * UpdateCookieDBListener impl:
403  * mozIStorageStatementCallback used to track asynchronous update operations.
404  ******************************************************************************/
405 class UpdateCookieDBListener final : public DBListenerErrorHandler {
406  private:
GetOpType()407   const char *GetOpType() override { return "UPDATE"; }
408 
409   ~UpdateCookieDBListener() = default;
410 
411  public:
412   NS_DECL_ISUPPORTS
413 
UpdateCookieDBListener(DBState * dbState)414   explicit UpdateCookieDBListener(DBState *dbState)
415       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)416   NS_IMETHOD HandleResult(mozIStorageResultSet *) override {
417     NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
418     return NS_OK;
419   }
HandleCompletion(uint16_t aReason)420   NS_IMETHOD HandleCompletion(uint16_t aReason) override { return NS_OK; }
421 };
422 
423 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
424 
425 /******************************************************************************
426  * RemoveCookieDBListener impl:
427  * mozIStorageStatementCallback used to track asynchronous removal operations.
428  ******************************************************************************/
429 class RemoveCookieDBListener final : public DBListenerErrorHandler {
430  private:
GetOpType()431   const char *GetOpType() override { return "REMOVE"; }
432 
433   ~RemoveCookieDBListener() = default;
434 
435  public:
436   NS_DECL_ISUPPORTS
437 
RemoveCookieDBListener(DBState * dbState)438   explicit RemoveCookieDBListener(DBState *dbState)
439       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)440   NS_IMETHOD HandleResult(mozIStorageResultSet *) override {
441     NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
442     return NS_OK;
443   }
HandleCompletion(uint16_t aReason)444   NS_IMETHOD HandleCompletion(uint16_t aReason) override { return NS_OK; }
445 };
446 
447 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
448 
449 /******************************************************************************
450  * CloseCookieDBListener imp:
451  * Static mozIStorageCompletionCallback used to notify when the database is
452  * successfully closed.
453  ******************************************************************************/
454 class CloseCookieDBListener final : public mozIStorageCompletionCallback {
455   ~CloseCookieDBListener() = default;
456 
457  public:
CloseCookieDBListener(DBState * dbState)458   explicit CloseCookieDBListener(DBState *dbState) : mDBState(dbState) {}
459   RefPtr<DBState> mDBState;
460   NS_DECL_ISUPPORTS
461 
Complete(nsresult,nsISupports *)462   NS_IMETHOD Complete(nsresult, nsISupports *) override {
463     gCookieService->HandleDBClosed(mDBState);
464     return NS_OK;
465   }
466 };
467 
468 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
469 
470 namespace {
471 
472 class AppClearDataObserver final : public nsIObserver {
473   ~AppClearDataObserver() = default;
474 
475  public:
476   NS_DECL_ISUPPORTS
477 
478   // nsIObserver implementation.
479   NS_IMETHOD
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)480   Observe(nsISupports *aSubject, const char *aTopic,
481           const char16_t *aData) override {
482     MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA));
483 
484     MOZ_ASSERT(XRE_IsParentProcess());
485 
486     nsCOMPtr<nsICookieManager> cookieManager =
487         do_GetService(NS_COOKIEMANAGER_CONTRACTID);
488     MOZ_ASSERT(cookieManager);
489 
490     return cookieManager->RemoveCookiesWithOriginAttributes(
491         nsDependentString(aData), EmptyCString());
492   }
493 };
494 
495 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
496 
497 }  // namespace
498 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const499 size_t nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
500   size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
501 
502   amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
503   for (uint32_t i = 0; i < mCookies.Length(); ++i) {
504     amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
505   }
506 
507   return amount;
508 }
509 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const510 size_t DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
511   size_t amount = 0;
512 
513   amount += aMallocSizeOf(this);
514   amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
515 
516   return amount;
517 }
518 
519 /******************************************************************************
520  * nsCookieService impl:
521  * singleton instance ctor/dtor methods
522  ******************************************************************************/
523 
GetXPCOMSingleton()524 already_AddRefed<nsICookieService> nsCookieService::GetXPCOMSingleton() {
525   if (IsNeckoChild()) return CookieServiceChild::GetSingleton();
526 
527   return GetSingleton();
528 }
529 
GetSingleton()530 already_AddRefed<nsCookieService> nsCookieService::GetSingleton() {
531   NS_ASSERTION(!IsNeckoChild(), "not a parent process");
532 
533   if (gCookieService) {
534     return do_AddRef(gCookieService);
535   }
536 
537   // Create a new singleton nsCookieService.
538   // We AddRef only once since XPCOM has rules about the ordering of module
539   // teardowns - by the time our module destructor is called, it's too late to
540   // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
541   // cycles have already been completed and would result in serious leaks.
542   // See bug 209571.
543   gCookieService = new nsCookieService();
544   if (gCookieService) {
545     if (NS_SUCCEEDED(gCookieService->Init())) {
546       ClearOnShutdown(&gCookieService);
547     } else {
548       gCookieService = nullptr;
549     }
550   }
551 
552   return do_AddRef(gCookieService);
553 }
554 
AppClearDataObserverInit()555 /* static */ void nsCookieService::AppClearDataObserverInit() {
556   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
557   nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
558   observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA,
559                                /* ownsWeak= */ false);
560 }
561 
562 /******************************************************************************
563  * nsCookieService impl:
564  * public methods
565  ******************************************************************************/
566 
NS_IMPL_ISUPPORTS(nsCookieService,nsICookieService,nsICookieManager,nsIObserver,nsISupportsWeakReference,nsIMemoryReporter)567 NS_IMPL_ISUPPORTS(nsCookieService, nsICookieService, nsICookieManager,
568                   nsIObserver, nsISupportsWeakReference, nsIMemoryReporter)
569 
570 nsCookieService::nsCookieService()
571     : mDBState(nullptr),
572       mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT),
573       mThirdPartySession(false),
574       mThirdPartyNonsecureSession(false),
575       mLeaveSecureAlone(true),
576       mMaxNumberOfCookies(kMaxNumberOfCookies),
577       mMaxCookiesPerHost(kMaxCookiesPerHost),
578       mCookiePurgeAge(kCookiePurgeAge),
579       mThread(nullptr),
580       mMonitor("CookieThread"),
581       mInitializedDBStates(false),
582       mInitializedDBConn(false) {}
583 
Init()584 nsresult nsCookieService::Init() {
585   nsresult rv;
586   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
587   NS_ENSURE_SUCCESS(rv, rv);
588 
589   mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
590   NS_ENSURE_SUCCESS(rv, rv);
591 
592   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
593   NS_ENSURE_SUCCESS(rv, rv);
594 
595   // init our pref and observer
596   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
597   if (prefBranch) {
598     prefBranch->AddObserver(kPrefCookieBehavior, this, true);
599     prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
600     prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
601     prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
602     prefBranch->AddObserver(kPrefThirdPartySession, this, true);
603     prefBranch->AddObserver(kPrefThirdPartyNonsecureSession, this, true);
604     prefBranch->AddObserver(kCookieLeaveSecurityAlone, this, true);
605     PrefChanged(prefBranch);
606   }
607 
608   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
609   NS_ENSURE_SUCCESS(rv, rv);
610 
611   // Init our default, and possibly private DBStates.
612   InitDBStates();
613 
614   RegisterWeakMemoryReporter(this);
615 
616   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
617   NS_ENSURE_STATE(os);
618   os->AddObserver(this, "profile-before-change", true);
619   os->AddObserver(this, "profile-do-change", true);
620   os->AddObserver(this, "last-pb-context-exited", true);
621 
622   mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
623   if (!mPermissionService) {
624     NS_WARNING(
625         "nsICookiePermission implementation not available - some features "
626         "won't work!");
627     COOKIE_LOGSTRING(
628         LogLevel::Warning,
629         ("Init(): nsICookiePermission implementation not available"));
630   }
631 
632   return NS_OK;
633 }
634 
InitDBStates()635 void nsCookieService::InitDBStates() {
636   NS_ASSERTION(!mDBState, "already have a DBState");
637   NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
638   NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
639   NS_ASSERTION(!mInitializedDBStates, "already initialized");
640   NS_ASSERTION(!mThread, "already have a cookie thread");
641 
642   // Create a new default DBState and set our current one.
643   mDefaultDBState = new DBState();
644   mDBState = mDefaultDBState;
645 
646   mPrivateDBState = new DBState();
647 
648   // Get our cookie file.
649   nsresult rv = NS_GetSpecialDirectory(
650       NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mDefaultDBState->cookieFile));
651   if (NS_FAILED(rv)) {
652     // We've already set up our DBStates appropriately; nothing more to do.
653     COOKIE_LOGSTRING(LogLevel::Warning,
654                      ("InitDBStates(): couldn't get cookie file"));
655 
656     mInitializedDBConn = true;
657     mInitializedDBStates = true;
658     return;
659   }
660   mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
661 
662   NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread)));
663 
664   nsCOMPtr<nsIRunnable> runnable =
665       NS_NewRunnableFunction("InitDBStates.TryInitDB", [] {
666         NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDBState &&
667                             gCookieService->mDefaultDBState);
668 
669         MonitorAutoLock lock(gCookieService->mMonitor);
670 
671         // Attempt to open and read the database. If TryInitDB() returns
672         // RESULT_RETRY, do so.
673         OpenDBResult result = gCookieService->TryInitDB(false);
674         if (result == RESULT_RETRY) {
675           // Database may be corrupt. Synchronously close the connection, clean
676           // up the default DBState, and try again.
677           COOKIE_LOGSTRING(LogLevel::Warning,
678                            ("InitDBStates(): retrying TryInitDB()"));
679           gCookieService->CleanupCachedStatements();
680           gCookieService->CleanupDefaultDBConnection();
681           result = gCookieService->TryInitDB(true);
682           if (result == RESULT_RETRY) {
683             // We're done. Change the code to failure so we clean up below.
684             result = RESULT_FAILURE;
685           }
686         }
687 
688         if (result == RESULT_FAILURE) {
689           COOKIE_LOGSTRING(
690               LogLevel::Warning,
691               ("InitDBStates(): TryInitDB() failed, closing connection"));
692 
693           // Connection failure is unrecoverable. Clean up our connection. We
694           // can run fine without persistent storage -- e.g. if there's no
695           // profile.
696           gCookieService->CleanupCachedStatements();
697           gCookieService->CleanupDefaultDBConnection();
698 
699           // No need to initialize dbConn
700           gCookieService->mInitializedDBConn = true;
701         }
702 
703         gCookieService->mInitializedDBStates = true;
704 
705         NS_DispatchToMainThread(
706             NS_NewRunnableFunction("TryInitDB.InitDBConn", [] {
707               NS_ENSURE_TRUE_VOID(gCookieService);
708               gCookieService->InitDBConn();
709             }));
710         gCookieService->mMonitor.Notify();
711       });
712 
713   mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
714 }
715 
716 namespace {
717 
718 class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction {
719   ~ConvertAppIdToOriginAttrsSQLFunction() = default;
720 
721   NS_DECL_ISUPPORTS
722   NS_DECL_MOZISTORAGEFUNCTION
723 };
724 
725 NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
726 
727 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)728 ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
729     mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult) {
730   nsresult rv;
731   int32_t inIsolatedMozBrowser;
732 
733   rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
734   NS_ENSURE_SUCCESS(rv, rv);
735 
736   // Create an originAttributes object by inIsolatedMozBrowser.
737   // Then create the originSuffix string from this object.
738   OriginAttributes attrs(nsIScriptSecurityManager::NO_APP_ID,
739                          (inIsolatedMozBrowser ? true : false));
740   nsAutoCString suffix;
741   attrs.CreateSuffix(suffix);
742 
743   RefPtr<nsVariant> outVar(new nsVariant());
744   rv = outVar->SetAsAUTF8String(suffix);
745   NS_ENSURE_SUCCESS(rv, rv);
746 
747   outVar.forget(aResult);
748   return NS_OK;
749 }
750 
751 class SetAppIdFromOriginAttributesSQLFunction final
752     : public mozIStorageFunction {
753   ~SetAppIdFromOriginAttributesSQLFunction() = default;
754 
755   NS_DECL_ISUPPORTS
756   NS_DECL_MOZISTORAGEFUNCTION
757 };
758 
759 NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
760 
761 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)762 SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
763     mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult) {
764   nsresult rv;
765   nsAutoCString suffix;
766   OriginAttributes attrs;
767 
768   rv = aFunctionArguments->GetUTF8String(0, suffix);
769   NS_ENSURE_SUCCESS(rv, rv);
770   bool success = attrs.PopulateFromSuffix(suffix);
771   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
772 
773   RefPtr<nsVariant> outVar(new nsVariant());
774   rv = outVar->SetAsInt32(attrs.mAppId);
775   NS_ENSURE_SUCCESS(rv, rv);
776 
777   outVar.forget(aResult);
778   return NS_OK;
779 }
780 
781 class SetInBrowserFromOriginAttributesSQLFunction final
782     : public mozIStorageFunction {
783   ~SetInBrowserFromOriginAttributesSQLFunction() = default;
784 
785   NS_DECL_ISUPPORTS
786   NS_DECL_MOZISTORAGEFUNCTION
787 };
788 
789 NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
790                   mozIStorageFunction);
791 
792 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)793 SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
794     mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult) {
795   nsresult rv;
796   nsAutoCString suffix;
797   OriginAttributes attrs;
798 
799   rv = aFunctionArguments->GetUTF8String(0, suffix);
800   NS_ENSURE_SUCCESS(rv, rv);
801   bool success = attrs.PopulateFromSuffix(suffix);
802   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
803 
804   RefPtr<nsVariant> outVar(new nsVariant());
805   rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
806   NS_ENSURE_SUCCESS(rv, rv);
807 
808   outVar.forget(aResult);
809   return NS_OK;
810 }
811 
812 }  // namespace
813 
814 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
815  * move the existing database file out of the way and create a new one.
816  *
817  * @returns RESULT_OK if opening or creating the database succeeded;
818  *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
819  *          other failure occurred that might be resolved by recreating the
820  *          database; or RESULT_FAILED if there was an unrecoverable error and
821  *          we must run without a database.
822  *
823  * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
824  * cleanup of the default DBState.
825  */
TryInitDB(bool aRecreateDB)826 OpenDBResult nsCookieService::TryInitDB(bool aRecreateDB) {
827   NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
828   NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
829   NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
830   NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
831   NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
832 
833   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
834   // want to delete it outright, since it may be useful for debugging purposes,
835   // so we move it out of the way.
836   nsresult rv;
837   if (aRecreateDB) {
838     nsCOMPtr<nsIFile> backupFile;
839     mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
840     rv = backupFile->MoveToNative(nullptr,
841                                   NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
842     NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
843   }
844 
845   // This block provides scope for the Telemetry AutoTimer
846   {
847     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
848         telemetry;
849     ReadAheadFile(mDefaultDBState->cookieFile);
850 
851     // open a connection to the cookie database, and only cache our connection
852     // and statements upon success. The connection is opened unshared to
853     // eliminate cache contention between the main and background threads.
854     rv = mStorageService->OpenUnsharedDatabase(
855         mDefaultDBState->cookieFile, getter_AddRefs(mDefaultDBState->syncConn));
856     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
857   }
858 
859   auto guard = MakeScopeExit([&] { mDefaultDBState->syncConn = nullptr; });
860 
861   bool tableExists = false;
862   mDefaultDBState->syncConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
863                                          &tableExists);
864   if (!tableExists) {
865     rv = CreateTable();
866     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
867 
868   } else {
869     // table already exists; check the schema version before reading
870     int32_t dbSchemaVersion;
871     rv = mDefaultDBState->syncConn->GetSchemaVersion(&dbSchemaVersion);
872     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
873 
874     // Start a transaction for the whole migration block.
875     mozStorageTransaction transaction(mDefaultDBState->syncConn, true);
876 
877     switch (dbSchemaVersion) {
878       // Upgrading.
879       // Every time you increment the database schema, you need to implement
880       // the upgrading code from the previous version to the new one. If
881       // migration fails for any reason, it's a bug -- so we return RESULT_RETRY
882       // such that the original database will be saved, in the hopes that we
883       // might one day see it and fix it.
884       case 1: {
885         // Add the lastAccessed column to the table.
886         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
887             "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
888         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
889       }
890         // Fall through to the next upgrade.
891         MOZ_FALLTHROUGH;
892 
893       case 2: {
894         // Add the baseDomain column and index to the table.
895         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
896             NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD baseDomain TEXT"));
897         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
898 
899         // Compute the baseDomains for the table. This must be done eagerly
900         // otherwise we won't be able to synchronously read in individual
901         // domains on demand.
902         const int64_t SCHEMA2_IDX_ID = 0;
903         const int64_t SCHEMA2_IDX_HOST = 1;
904         nsCOMPtr<mozIStorageStatement> select;
905         rv = mDefaultDBState->syncConn->CreateStatement(
906             NS_LITERAL_CSTRING("SELECT id, host FROM moz_cookies"),
907             getter_AddRefs(select));
908         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
909 
910         nsCOMPtr<mozIStorageStatement> update;
911         rv = mDefaultDBState->syncConn->CreateStatement(
912             NS_LITERAL_CSTRING("UPDATE moz_cookies SET baseDomain = "
913                                ":baseDomain WHERE id = :id"),
914             getter_AddRefs(update));
915         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
916 
917         nsCString baseDomain, host;
918         bool hasResult;
919         while (true) {
920           rv = select->ExecuteStep(&hasResult);
921           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
922 
923           if (!hasResult) break;
924 
925           int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
926           select->GetUTF8String(SCHEMA2_IDX_HOST, host);
927 
928           rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
929           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
930 
931           mozStorageStatementScoper scoper(update);
932 
933           rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
934                                             baseDomain);
935           NS_ASSERT_SUCCESS(rv);
936           rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
937           NS_ASSERT_SUCCESS(rv);
938 
939           rv = update->ExecuteStep(&hasResult);
940           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
941         }
942 
943         // Create an index on baseDomain.
944         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
945             "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
946         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
947       }
948         // Fall through to the next upgrade.
949         MOZ_FALLTHROUGH;
950 
951       case 3: {
952         // Add the creationTime column to the table, and create a unique index
953         // on (name, host, path). Before we do this, we have to purge the table
954         // of expired cookies such that we know that the (name, host, path)
955         // index is truly unique -- otherwise we can't create the index. Note
956         // that we can't just execute a statement to delete all rows where the
957         // expiry column is in the past -- doing so would rely on the clock
958         // (both now and when previous cookies were set) being monotonic.
959 
960         // Select the whole table, and order by the fields we're interested in.
961         // This means we can simply do a linear traversal of the results and
962         // check for duplicates as we go.
963         const int64_t SCHEMA3_IDX_ID = 0;
964         const int64_t SCHEMA3_IDX_NAME = 1;
965         const int64_t SCHEMA3_IDX_HOST = 2;
966         const int64_t SCHEMA3_IDX_PATH = 3;
967         nsCOMPtr<mozIStorageStatement> select;
968         rv = mDefaultDBState->syncConn->CreateStatement(
969             NS_LITERAL_CSTRING(
970                 "SELECT id, name, host, path FROM moz_cookies "
971                 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
972             getter_AddRefs(select));
973         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
974 
975         nsCOMPtr<mozIStorageStatement> deleteExpired;
976         rv = mDefaultDBState->syncConn->CreateStatement(
977             NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE id = :id"),
978             getter_AddRefs(deleteExpired));
979         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
980 
981         // Read the first row.
982         bool hasResult;
983         rv = select->ExecuteStep(&hasResult);
984         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
985 
986         if (hasResult) {
987           nsCString name1, host1, path1;
988           int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
989           select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
990           select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
991           select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
992 
993           nsCString name2, host2, path2;
994           while (true) {
995             // Read the second row.
996             rv = select->ExecuteStep(&hasResult);
997             NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
998 
999             if (!hasResult) break;
1000 
1001             int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
1002             select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
1003             select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
1004             select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
1005 
1006             // If the two rows match in (name, host, path), we know the earlier
1007             // row has an earlier expiry time. Delete it.
1008             if (name1 == name2 && host1 == host2 && path1 == path2) {
1009               mozStorageStatementScoper scoper(deleteExpired);
1010 
1011               rv =
1012                   deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"), id1);
1013               NS_ASSERT_SUCCESS(rv);
1014 
1015               rv = deleteExpired->ExecuteStep(&hasResult);
1016               NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1017             }
1018 
1019             // Make the second row the first for the next iteration.
1020             name1 = name2;
1021             host1 = host2;
1022             path1 = path2;
1023             id1 = id2;
1024           }
1025         }
1026 
1027         // Add the creationTime column to the table.
1028         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1029             "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
1030         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1031 
1032         // Copy the id of each row into the new creationTime column.
1033         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1034             NS_LITERAL_CSTRING("UPDATE moz_cookies SET creationTime = "
1035                                "(SELECT id WHERE id = moz_cookies.id)"));
1036         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1037 
1038         // Create a unique index on (name, host, path) to allow fast lookup.
1039         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1040             NS_LITERAL_CSTRING("CREATE UNIQUE INDEX moz_uniqueid "
1041                                "ON moz_cookies (name, host, path)"));
1042         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1043       }
1044         // Fall through to the next upgrade.
1045         MOZ_FALLTHROUGH;
1046 
1047       case 4: {
1048         // We need to add appId/inBrowserElement, plus change a constraint on
1049         // the table (unique entries now include appId/inBrowserElement):
1050         // this requires creating a new table and copying the data to it.  We
1051         // then rename the new table to the old name.
1052         //
1053         // Why we made this change: appId/inBrowserElement allow "cookie jars"
1054         // for Firefox OS. We create a separate cookie namespace per {appId,
1055         // inBrowserElement}.  When upgrading, we convert existing cookies
1056         // (which imply we're on desktop/mobile) to use {0, false}, as that is
1057         // the only namespace used by a non-Firefox-OS implementation.
1058 
1059         // Rename existing table
1060         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1061             "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1062         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1063 
1064         // Drop existing index (CreateTable will create new one for new table)
1065         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1066             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1067         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1068 
1069         // Create new table (with new fields and new unique constraint)
1070         rv = CreateTableForSchemaVersion5();
1071         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1072 
1073         // Copy data from old table, using appId/inBrowser=0 for existing rows
1074         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1075             "INSERT INTO moz_cookies "
1076             "(baseDomain, appId, inBrowserElement, name, value, host, path, "
1077             "expiry,"
1078             " lastAccessed, creationTime, isSecure, isHttpOnly) "
1079             "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
1080             " lastAccessed, creationTime, isSecure, isHttpOnly "
1081             "FROM moz_cookies_old"));
1082         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1083 
1084         // Drop old table
1085         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1086             NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old"));
1087         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1088 
1089         COOKIE_LOGSTRING(LogLevel::Debug,
1090                          ("Upgraded database to schema version 5"));
1091       }
1092         // Fall through to the next upgrade.
1093         MOZ_FALLTHROUGH;
1094 
1095       case 5: {
1096         // Change in the version: Replace the columns |appId| and
1097         // |inBrowserElement| by a single column |originAttributes|.
1098         //
1099         // Why we made this change: FxOS new security model (NSec) encapsulates
1100         // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to
1101         // make it easier to modify the contents of this structure in the
1102         // future.
1103         //
1104         // We do the migration in several steps:
1105         // 1. Rename the old table.
1106         // 2. Create a new table.
1107         // 3. Copy data from the old table to the new table; convert appId and
1108         //    inBrowserElement to originAttributes in the meantime.
1109 
1110         // Rename existing table.
1111         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1112             "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1113         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1114 
1115         // Drop existing index (CreateTable will create new one for new table).
1116         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1117             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1118         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1119 
1120         // Create new table with new fields and new unique constraint.
1121         rv = CreateTableForSchemaVersion6();
1122         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1123 
1124         // Copy data from old table without the two deprecated columns appId and
1125         // inBrowserElement.
1126         nsCOMPtr<mozIStorageFunction> convertToOriginAttrs(
1127             new ConvertAppIdToOriginAttrsSQLFunction());
1128         NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
1129 
1130         NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
1131                                  "CONVERT_TO_ORIGIN_ATTRIBUTES");
1132 
1133         rv = mDefaultDBState->syncConn->CreateFunction(convertToOriginAttrsName,
1134                                                        2, convertToOriginAttrs);
1135         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1136 
1137         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1138             "INSERT INTO moz_cookies "
1139             "(baseDomain, originAttributes, name, value, host, path, expiry,"
1140             " lastAccessed, creationTime, isSecure, isHttpOnly) "
1141             "SELECT baseDomain, "
1142             " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
1143             " name, value, host, path, expiry, lastAccessed, creationTime, "
1144             " isSecure, isHttpOnly "
1145             "FROM moz_cookies_old"));
1146         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1147 
1148         rv =
1149             mDefaultDBState->syncConn->RemoveFunction(convertToOriginAttrsName);
1150         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1151 
1152         // Drop old table
1153         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1154             NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old"));
1155         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1156 
1157         COOKIE_LOGSTRING(LogLevel::Debug,
1158                          ("Upgraded database to schema version 6"));
1159       }
1160         MOZ_FALLTHROUGH;
1161 
1162       case 6: {
1163         // We made a mistake in schema version 6. We cannot remove expected
1164         // columns of any version (checked in the default case) from cookie
1165         // database, because doing this would destroy the possibility of
1166         // downgrading database.
1167         //
1168         // This version simply restores appId and inBrowserElement columns in
1169         // order to fix downgrading issue even though these two columns are no
1170         // longer used in the latest schema.
1171         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1172             "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
1173         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1174 
1175         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1176             "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
1177         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1178 
1179         // Compute and populate the values of appId and inBrwoserElement from
1180         // originAttributes.
1181         nsCOMPtr<mozIStorageFunction> setAppId(
1182             new SetAppIdFromOriginAttributesSQLFunction());
1183         NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
1184 
1185         NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
1186 
1187         rv = mDefaultDBState->syncConn->CreateFunction(setAppIdName, 1,
1188                                                        setAppId);
1189         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1190 
1191         nsCOMPtr<mozIStorageFunction> setInBrowser(
1192             new SetInBrowserFromOriginAttributesSQLFunction());
1193         NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
1194 
1195         NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
1196 
1197         rv = mDefaultDBState->syncConn->CreateFunction(setInBrowserName, 1,
1198                                                        setInBrowser);
1199         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1200 
1201         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1202             "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
1203             "inBrowserElement = SET_IN_BROWSER(originAttributes);"));
1204         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1205 
1206         rv = mDefaultDBState->syncConn->RemoveFunction(setAppIdName);
1207         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1208 
1209         rv = mDefaultDBState->syncConn->RemoveFunction(setInBrowserName);
1210         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1211 
1212         COOKIE_LOGSTRING(LogLevel::Debug,
1213                          ("Upgraded database to schema version 7"));
1214       }
1215         MOZ_FALLTHROUGH;
1216 
1217       case 7: {
1218         // Remove the appId field from moz_cookies.
1219         //
1220         // Unfortunately sqlite doesn't support dropping columns using ALTER
1221         // TABLE, so we need to go through the procedure documented in
1222         // https://www.sqlite.org/lang_altertable.html.
1223 
1224         // Drop existing index
1225         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1226             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1227         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1228 
1229         // Create a new_moz_cookies table without the appId field.
1230         rv = CreateTableWorker("new_moz_cookies");
1231         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1232 
1233         // Move the data over.
1234         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1235             NS_LITERAL_CSTRING("INSERT INTO new_moz_cookies ("
1236                                "id, "
1237                                "baseDomain, "
1238                                "originAttributes, "
1239                                "name, "
1240                                "value, "
1241                                "host, "
1242                                "path, "
1243                                "expiry, "
1244                                "lastAccessed, "
1245                                "creationTime, "
1246                                "isSecure, "
1247                                "isHttpOnly, "
1248                                "inBrowserElement "
1249                                ") SELECT "
1250                                "id, "
1251                                "baseDomain, "
1252                                "originAttributes, "
1253                                "name, "
1254                                "value, "
1255                                "host, "
1256                                "path, "
1257                                "expiry, "
1258                                "lastAccessed, "
1259                                "creationTime, "
1260                                "isSecure, "
1261                                "isHttpOnly, "
1262                                "inBrowserElement "
1263                                "FROM moz_cookies;"));
1264         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1265 
1266         // Drop the old table
1267         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1268             NS_LITERAL_CSTRING("DROP TABLE moz_cookies;"));
1269         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1270 
1271         // Rename new_moz_cookies to moz_cookies.
1272         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1273             "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
1274         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1275 
1276         // Recreate our index.
1277         rv = CreateIndex();
1278         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1279 
1280         COOKIE_LOGSTRING(LogLevel::Debug,
1281                          ("Upgraded database to schema version 8"));
1282       }
1283         MOZ_FALLTHROUGH;
1284 
1285       case 8: {
1286         // Add the sameSite column to the table.
1287         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1288             NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
1289         COOKIE_LOGSTRING(LogLevel::Debug,
1290                          ("Upgraded database to schema version 9"));
1291       }
1292 
1293         // No more upgrades. Update the schema version.
1294         rv =
1295             mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1296         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1297 
1298         Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_OLD_SCHEMA,
1299                               dbSchemaVersion);
1300         MOZ_FALLTHROUGH;
1301 
1302       case COOKIES_SCHEMA_VERSION:
1303         break;
1304 
1305       case 0: {
1306         NS_WARNING("couldn't get schema version!");
1307 
1308         // the table may be usable; someone might've just clobbered the schema
1309         // version. we can treat this case like a downgrade using the codepath
1310         // below, by verifying the columns we care about are all there. for now,
1311         // re-set the schema version in the db, in case the checks succeed (if
1312         // they don't, we're dropping the table anyway).
1313         rv =
1314             mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1315         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1316       }
1317         // fall through to downgrade check
1318         MOZ_FALLTHROUGH;
1319 
1320       // downgrading.
1321       // if columns have been added to the table, we can still use the ones we
1322       // understand safely. if columns have been deleted or altered, just
1323       // blow away the table and start from scratch! if you change the way
1324       // a column is interpreted, make sure you also change its name so this
1325       // check will catch it.
1326       default: {
1327         // check if all the expected columns exist
1328         nsCOMPtr<mozIStorageStatement> stmt;
1329         rv = mDefaultDBState->syncConn->CreateStatement(
1330             NS_LITERAL_CSTRING("SELECT "
1331                                "id, "
1332                                "baseDomain, "
1333                                "originAttributes, "
1334                                "name, "
1335                                "value, "
1336                                "host, "
1337                                "path, "
1338                                "expiry, "
1339                                "lastAccessed, "
1340                                "creationTime, "
1341                                "isSecure, "
1342                                "isHttpOnly, "
1343                                "sameSite "
1344                                "FROM moz_cookies"),
1345             getter_AddRefs(stmt));
1346         if (NS_SUCCEEDED(rv)) break;
1347 
1348         // our columns aren't there - drop the table!
1349         rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1350             NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
1351         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1352 
1353         rv = CreateTable();
1354         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1355       } break;
1356     }
1357   }
1358 
1359   // if we deleted a corrupt db, don't attempt to import - return now
1360   if (aRecreateDB) {
1361     return RESULT_OK;
1362   }
1363 
1364   // check whether to import or just read in the db
1365   if (tableExists) {
1366     return Read();
1367   }
1368 
1369   nsCOMPtr<nsIRunnable> runnable =
1370       NS_NewRunnableFunction("TryInitDB.ImportCookies", [] {
1371         NS_ENSURE_TRUE_VOID(gCookieService);
1372         NS_ENSURE_TRUE_VOID(gCookieService->mDefaultDBState);
1373         nsCOMPtr<nsIFile> oldCookieFile;
1374         nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1375                                              getter_AddRefs(oldCookieFile));
1376         if (NS_FAILED(rv)) {
1377           return;
1378         }
1379 
1380         // Import cookies, and clean up the old file regardless of success or
1381         // failure. Note that we have to switch out our DBState temporarily, in
1382         // case we're in private browsing mode; otherwise ImportCookies() won't
1383         // be happy.
1384         DBState *initialState = gCookieService->mDBState;
1385         gCookieService->mDBState = gCookieService->mDefaultDBState;
1386         oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
1387         gCookieService->ImportCookies(oldCookieFile);
1388         oldCookieFile->Remove(false);
1389         gCookieService->mDBState = initialState;
1390       });
1391 
1392   NS_DispatchToMainThread(runnable);
1393 
1394   return RESULT_OK;
1395 }
1396 
InitDBConn()1397 void nsCookieService::InitDBConn() {
1398   MOZ_ASSERT(NS_IsMainThread());
1399 
1400   // We should skip InitDBConn if we close profile during initializing DBStates
1401   // and then InitDBConn is called after we close the DBStates.
1402   if (!mInitializedDBStates || mInitializedDBConn || !mDefaultDBState) {
1403     return;
1404   }
1405 
1406   for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
1407     CookieDomainTuple &tuple = mReadArray[i];
1408     RefPtr<nsCookie> cookie = nsCookie::Create(
1409         tuple.cookie->name, tuple.cookie->value, tuple.cookie->host,
1410         tuple.cookie->path, tuple.cookie->expiry, tuple.cookie->lastAccessed,
1411         tuple.cookie->creationTime, false, tuple.cookie->isSecure,
1412         tuple.cookie->isHttpOnly, tuple.cookie->originAttributes,
1413         tuple.cookie->sameSite);
1414 
1415     AddCookieToList(tuple.key, cookie, mDefaultDBState, nullptr, false);
1416   }
1417 
1418   if (NS_FAILED(InitDBConnInternal())) {
1419     COOKIE_LOGSTRING(LogLevel::Warning,
1420                      ("InitDBConn(): retrying InitDBConnInternal()"));
1421     CleanupCachedStatements();
1422     CleanupDefaultDBConnection();
1423     if (NS_FAILED(InitDBConnInternal())) {
1424       COOKIE_LOGSTRING(
1425           LogLevel::Warning,
1426           ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
1427 
1428       // Game over, clean the connections.
1429       CleanupCachedStatements();
1430       CleanupDefaultDBConnection();
1431     }
1432   }
1433   mInitializedDBConn = true;
1434 
1435   COOKIE_LOGSTRING(LogLevel::Debug,
1436                    ("InitDBConn(): mInitializedDBConn = true"));
1437   mEndInitDBConn = mozilla::TimeStamp::Now();
1438 
1439   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1440   if (os) {
1441     os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
1442     mReadArray.Clear();
1443   }
1444 }
1445 
InitDBConnInternal()1446 nsresult nsCookieService::InitDBConnInternal() {
1447   MOZ_ASSERT(NS_IsMainThread());
1448 
1449   nsresult rv = mStorageService->OpenUnsharedDatabase(
1450       mDefaultDBState->cookieFile, getter_AddRefs(mDefaultDBState->dbConn));
1451   NS_ENSURE_SUCCESS(rv, rv);
1452 
1453   // Set up our listeners.
1454   mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
1455   mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
1456   mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
1457   mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
1458 
1459   // Grow cookie db in 512KB increments
1460   mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
1461 
1462   // make operations on the table asynchronous, for performance
1463   mDefaultDBState->dbConn->ExecuteSimpleSQL(
1464       NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
1465 
1466   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1467   // 16 pages (around 500KB).
1468   mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1469       MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
1470   mDefaultDBState->dbConn->ExecuteSimpleSQL(
1471       NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = 16"));
1472 
1473   // cache frequently used statements (for insertion, deletion, and updating)
1474   rv = mDefaultDBState->dbConn->CreateAsyncStatement(
1475       NS_LITERAL_CSTRING("INSERT INTO moz_cookies ("
1476                          "baseDomain, "
1477                          "originAttributes, "
1478                          "name, "
1479                          "value, "
1480                          "host, "
1481                          "path, "
1482                          "expiry, "
1483                          "lastAccessed, "
1484                          "creationTime, "
1485                          "isSecure, "
1486                          "isHttpOnly, "
1487                          "sameSite "
1488                          ") VALUES ("
1489                          ":baseDomain, "
1490                          ":originAttributes, "
1491                          ":name, "
1492                          ":value, "
1493                          ":host, "
1494                          ":path, "
1495                          ":expiry, "
1496                          ":lastAccessed, "
1497                          ":creationTime, "
1498                          ":isSecure, "
1499                          ":isHttpOnly, "
1500                          ":sameSite"
1501                          ")"),
1502       getter_AddRefs(mDefaultDBState->stmtInsert));
1503   NS_ENSURE_SUCCESS(rv, rv);
1504 
1505   rv = mDefaultDBState->dbConn->CreateAsyncStatement(
1506       NS_LITERAL_CSTRING("DELETE FROM moz_cookies "
1507                          "WHERE name = :name AND host = :host AND path = :path "
1508                          "AND originAttributes = :originAttributes"),
1509       getter_AddRefs(mDefaultDBState->stmtDelete));
1510   NS_ENSURE_SUCCESS(rv, rv);
1511 
1512   rv = mDefaultDBState->dbConn->CreateAsyncStatement(
1513       NS_LITERAL_CSTRING("UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1514                          "WHERE name = :name AND host = :host AND path = :path "
1515                          "AND originAttributes = :originAttributes"),
1516       getter_AddRefs(mDefaultDBState->stmtUpdate));
1517   return rv;
1518 }
1519 
1520 // Sets the schema version and creates the moz_cookies table.
CreateTableWorker(const char * aName)1521 nsresult nsCookieService::CreateTableWorker(const char *aName) {
1522   // Create the table.
1523   // We default originAttributes to empty string: this is so if users revert to
1524   // an older Firefox version that doesn't know about this field, any cookies
1525   // set will still work once they upgrade back.
1526   nsAutoCString command("CREATE TABLE ");
1527   command.Append(aName);
1528   command.AppendLiteral(
1529       " ("
1530       "id INTEGER PRIMARY KEY, "
1531       "baseDomain TEXT, "
1532       "originAttributes TEXT NOT NULL DEFAULT '', "
1533       "name TEXT, "
1534       "value TEXT, "
1535       "host TEXT, "
1536       "path TEXT, "
1537       "expiry INTEGER, "
1538       "lastAccessed INTEGER, "
1539       "creationTime INTEGER, "
1540       "isSecure INTEGER, "
1541       "isHttpOnly INTEGER, "
1542       "inBrowserElement INTEGER DEFAULT 0, "
1543       "sameSite INTEGER DEFAULT 0, "
1544       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1545       ")");
1546   return mDefaultDBState->syncConn->ExecuteSimpleSQL(command);
1547 }
1548 
1549 // Sets the schema version and creates the moz_cookies table.
CreateTable()1550 nsresult nsCookieService::CreateTable() {
1551   // Set the schema version, before creating the table.
1552   nsresult rv =
1553       mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1554   if (NS_FAILED(rv)) return rv;
1555 
1556   rv = CreateTableWorker("moz_cookies");
1557   if (NS_FAILED(rv)) return rv;
1558 
1559   return CreateIndex();
1560 }
1561 
CreateIndex()1562 nsresult nsCookieService::CreateIndex() {
1563   // Create an index on baseDomain.
1564   return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1565       "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1566       "originAttributes)"));
1567 }
1568 
1569 // Sets the schema version and creates the moz_cookies table.
CreateTableForSchemaVersion6()1570 nsresult nsCookieService::CreateTableForSchemaVersion6() {
1571   // Set the schema version, before creating the table.
1572   nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(6);
1573   if (NS_FAILED(rv)) return rv;
1574 
1575   // Create the table.
1576   // We default originAttributes to empty string: this is so if users revert to
1577   // an older Firefox version that doesn't know about this field, any cookies
1578   // set will still work once they upgrade back.
1579   rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1580       "CREATE TABLE moz_cookies ("
1581       "id INTEGER PRIMARY KEY, "
1582       "baseDomain TEXT, "
1583       "originAttributes TEXT NOT NULL DEFAULT '', "
1584       "name TEXT, "
1585       "value TEXT, "
1586       "host TEXT, "
1587       "path TEXT, "
1588       "expiry INTEGER, "
1589       "lastAccessed INTEGER, "
1590       "creationTime INTEGER, "
1591       "isSecure INTEGER, "
1592       "isHttpOnly INTEGER, "
1593       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1594       ")"));
1595   if (NS_FAILED(rv)) return rv;
1596 
1597   // Create an index on baseDomain.
1598   return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1599       "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1600       "originAttributes)"));
1601 }
1602 
1603 // Sets the schema version and creates the moz_cookies table.
CreateTableForSchemaVersion5()1604 nsresult nsCookieService::CreateTableForSchemaVersion5() {
1605   // Set the schema version, before creating the table.
1606   nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(5);
1607   if (NS_FAILED(rv)) return rv;
1608 
1609   // Create the table. We default appId/inBrowserElement to 0: this is so if
1610   // users revert to an older Firefox version that doesn't know about these
1611   // fields, any cookies set will still work once they upgrade back.
1612   rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
1613       NS_LITERAL_CSTRING("CREATE TABLE moz_cookies ("
1614                          "id INTEGER PRIMARY KEY, "
1615                          "baseDomain TEXT, "
1616                          "appId INTEGER DEFAULT 0, "
1617                          "inBrowserElement INTEGER DEFAULT 0, "
1618                          "name TEXT, "
1619                          "value TEXT, "
1620                          "host TEXT, "
1621                          "path TEXT, "
1622                          "expiry INTEGER, "
1623                          "lastAccessed INTEGER, "
1624                          "creationTime INTEGER, "
1625                          "isSecure INTEGER, "
1626                          "isHttpOnly INTEGER, "
1627                          "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, "
1628                          "appId, inBrowserElement)"
1629                          ")"));
1630   if (NS_FAILED(rv)) return rv;
1631 
1632   // Create an index on baseDomain.
1633   return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1634       "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1635       "appId, "
1636       "inBrowserElement)"));
1637 }
1638 
CloseDBStates()1639 void nsCookieService::CloseDBStates() {
1640   // return if we already closed
1641   if (!mDBState) {
1642     return;
1643   }
1644 
1645   if (mThread) {
1646     mThread->Shutdown();
1647     mThread = nullptr;
1648   }
1649 
1650   // Null out our private and pointer DBStates regardless.
1651   mPrivateDBState = nullptr;
1652   mDBState = nullptr;
1653 
1654   // If we don't have a default DBState, we're done.
1655   if (!mDefaultDBState) return;
1656 
1657   // Cleanup cached statements before we can close anything.
1658   CleanupCachedStatements();
1659 
1660   if (mDefaultDBState->dbConn) {
1661     // Asynchronously close the connection. We will null it below.
1662     mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1663   }
1664 
1665   CleanupDefaultDBConnection();
1666 
1667   mDefaultDBState = nullptr;
1668   mInitializedDBConn = false;
1669   mInitializedDBStates = false;
1670 }
1671 
1672 // Null out the statements.
1673 // This must be done before closing the connection.
CleanupCachedStatements()1674 void nsCookieService::CleanupCachedStatements() {
1675   mDefaultDBState->stmtInsert = nullptr;
1676   mDefaultDBState->stmtDelete = nullptr;
1677   mDefaultDBState->stmtUpdate = nullptr;
1678 }
1679 
1680 // Null out the listeners, and the database connection itself. This
1681 // will not null out the statements, cancel a pending read or
1682 // asynchronously close the connection -- these must be done
1683 // beforehand if necessary.
CleanupDefaultDBConnection()1684 void nsCookieService::CleanupDefaultDBConnection() {
1685   MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
1686   MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
1687   MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
1688 
1689   // Null out the database connections. If 'dbConn' has not been used for any
1690   // asynchronous operations yet, this will synchronously close it; otherwise,
1691   // it's expected that the caller has performed an AsyncClose prior.
1692   mDefaultDBState->dbConn = nullptr;
1693 
1694   // Manually null out our listeners. This is necessary because they hold a
1695   // strong ref to the DBState itself. They'll stay alive until whatever
1696   // statements are still executing complete.
1697   mDefaultDBState->insertListener = nullptr;
1698   mDefaultDBState->updateListener = nullptr;
1699   mDefaultDBState->removeListener = nullptr;
1700   mDefaultDBState->closeListener = nullptr;
1701 }
1702 
HandleDBClosed(DBState * aDBState)1703 void nsCookieService::HandleDBClosed(DBState *aDBState) {
1704   COOKIE_LOGSTRING(LogLevel::Debug,
1705                    ("HandleDBClosed(): DBState %p closed", aDBState));
1706 
1707   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1708 
1709   switch (aDBState->corruptFlag) {
1710     case DBState::OK: {
1711       // Database is healthy. Notify of closure.
1712       if (os) {
1713         os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1714       }
1715       break;
1716     }
1717     case DBState::CLOSING_FOR_REBUILD: {
1718       // Our close finished. Start the rebuild, and notify of db closure later.
1719       RebuildCorruptDB(aDBState);
1720       break;
1721     }
1722     case DBState::REBUILDING: {
1723       // We encountered an error during rebuild, closed the database, and now
1724       // here we are. We already have a 'cookies.sqlite.bak' from the original
1725       // dead database; we don't want to overwrite it, so let's move this one to
1726       // 'cookies.sqlite.bak-rebuild'.
1727       nsCOMPtr<nsIFile> backupFile;
1728       aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
1729       nsresult rv = backupFile->MoveToNative(
1730           nullptr, NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
1731 
1732       COOKIE_LOGSTRING(LogLevel::Warning,
1733                        ("HandleDBClosed(): DBState %p encountered error "
1734                         "rebuilding db; move to "
1735                         "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
1736                         aDBState, static_cast<uint32_t>(rv)));
1737       if (os) {
1738         os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1739       }
1740       break;
1741     }
1742   }
1743 }
1744 
HandleCorruptDB(DBState * aDBState)1745 void nsCookieService::HandleCorruptDB(DBState *aDBState) {
1746   if (mDefaultDBState != aDBState) {
1747     // We've either closed the state or we've switched profiles. It's getting
1748     // a bit late to rebuild -- bail instead.
1749     COOKIE_LOGSTRING(
1750         LogLevel::Warning,
1751         ("HandleCorruptDB(): DBState %p is already closed, aborting",
1752          aDBState));
1753     return;
1754   }
1755 
1756   COOKIE_LOGSTRING(LogLevel::Debug,
1757                    ("HandleCorruptDB(): DBState %p has corruptFlag %u",
1758                     aDBState, aDBState->corruptFlag));
1759 
1760   // Mark the database corrupt, so the close listener can begin reconstructing
1761   // it.
1762   switch (mDefaultDBState->corruptFlag) {
1763     case DBState::OK: {
1764       // Move to 'closing' state.
1765       mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
1766 
1767       CleanupCachedStatements();
1768       mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1769       CleanupDefaultDBConnection();
1770       break;
1771     }
1772     case DBState::CLOSING_FOR_REBUILD: {
1773       // We had an error while waiting for close completion. That's OK, just
1774       // ignore it -- we're rebuilding anyway.
1775       return;
1776     }
1777     case DBState::REBUILDING: {
1778       // We had an error while rebuilding the DB. Game over. Close the database
1779       // and let the close handler do nothing; then we'll move it out of the
1780       // way.
1781       CleanupCachedStatements();
1782       if (mDefaultDBState->dbConn) {
1783         mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
1784       }
1785       CleanupDefaultDBConnection();
1786       break;
1787     }
1788   }
1789 }
1790 
RebuildCorruptDB(DBState * aDBState)1791 void nsCookieService::RebuildCorruptDB(DBState *aDBState) {
1792   NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
1793   NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
1794                "should be in CLOSING_FOR_REBUILD state");
1795 
1796   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1797 
1798   aDBState->corruptFlag = DBState::REBUILDING;
1799 
1800   if (mDefaultDBState != aDBState) {
1801     // We've either closed the state or we've switched profiles. It's getting
1802     // a bit late to rebuild -- bail instead. In any case, we were waiting
1803     // on rebuild completion to notify of the db closure, which won't happen --
1804     // do so now.
1805     COOKIE_LOGSTRING(
1806         LogLevel::Warning,
1807         ("RebuildCorruptDB(): DBState %p is stale, aborting", aDBState));
1808     if (os) {
1809       os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1810     }
1811     return;
1812   }
1813 
1814   COOKIE_LOGSTRING(LogLevel::Debug,
1815                    ("RebuildCorruptDB(): creating new database"));
1816 
1817   nsCOMPtr<nsIRunnable> runnable =
1818       NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [] {
1819         NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDefaultDBState);
1820 
1821         // The database has been closed, and we're ready to rebuild. Open a
1822         // connection.
1823         OpenDBResult result = gCookieService->TryInitDB(true);
1824 
1825         nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction(
1826             "RebuildCorruptDB.TryInitDBComplete", [result] {
1827               NS_ENSURE_TRUE_VOID(gCookieService &&
1828                                   gCookieService->mDefaultDBState);
1829 
1830               nsCOMPtr<nsIObserverService> os =
1831                   mozilla::services::GetObserverService();
1832               if (result != RESULT_OK) {
1833                 // We're done. Reset our DB connection and statements, and
1834                 // notify of closure.
1835                 COOKIE_LOGSTRING(
1836                     LogLevel::Warning,
1837                     ("RebuildCorruptDB(): TryInitDB() failed with result %u",
1838                      result));
1839                 gCookieService->CleanupCachedStatements();
1840                 gCookieService->CleanupDefaultDBConnection();
1841                 gCookieService->mDefaultDBState->corruptFlag = DBState::OK;
1842                 if (os) {
1843                   os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1844                 }
1845                 return;
1846               }
1847 
1848               // Notify observers that we're beginning the rebuild.
1849               if (os) {
1850                 os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1851               }
1852 
1853               gCookieService->InitDBConnInternal();
1854 
1855               // Enumerate the hash, and add cookies to the params array.
1856               mozIStorageAsyncStatement *stmt =
1857                   gCookieService->mDefaultDBState->stmtInsert;
1858               nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1859               stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1860               for (auto iter =
1861                        gCookieService->mDefaultDBState->hostTable.Iter();
1862                    !iter.Done(); iter.Next()) {
1863                 nsCookieEntry *entry = iter.Get();
1864 
1865                 const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
1866                 for (nsCookieEntry::IndexType i = 0; i < cookies.Length();
1867                      ++i) {
1868                   nsCookie *cookie = cookies[i];
1869 
1870                   if (!cookie->IsSession()) {
1871                     bindCookieParameters(paramsArray, nsCookieKey(entry),
1872                                          cookie);
1873                   }
1874                 }
1875               }
1876 
1877               // Make sure we've got something to write. If we don't, we're
1878               // done.
1879               uint32_t length;
1880               paramsArray->GetLength(&length);
1881               if (length == 0) {
1882                 COOKIE_LOGSTRING(
1883                     LogLevel::Debug,
1884                     ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1885                 gCookieService->mDefaultDBState->corruptFlag = DBState::OK;
1886                 return;
1887               }
1888 
1889               // Execute the statement. If any errors crop up, we won't try
1890               // again.
1891               DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
1892               NS_ASSERT_SUCCESS(rv);
1893               nsCOMPtr<mozIStoragePendingStatement> handle;
1894               rv = stmt->ExecuteAsync(
1895                   gCookieService->mDefaultDBState->insertListener,
1896                   getter_AddRefs(handle));
1897               NS_ASSERT_SUCCESS(rv);
1898             });
1899         NS_DispatchToMainThread(innerRunnable);
1900       });
1901   mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1902 }
1903 
~nsCookieService()1904 nsCookieService::~nsCookieService() {
1905   CloseDBStates();
1906 
1907   UnregisterWeakMemoryReporter(this);
1908 
1909   gCookieService = nullptr;
1910 }
1911 
1912 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1913 nsCookieService::Observe(nsISupports *aSubject, const char *aTopic,
1914                          const char16_t *aData) {
1915   // check the topic
1916   if (!strcmp(aTopic, "profile-before-change")) {
1917     // The profile is about to change,
1918     // or is going away because the application is shutting down.
1919 
1920     // Close the default DB connection and null out our DBStates before
1921     // changing.
1922     CloseDBStates();
1923 
1924   } else if (!strcmp(aTopic, "profile-do-change")) {
1925     NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
1926     NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
1927 
1928     // the profile has already changed; init the db from the new location.
1929     // if we are in the private browsing state, however, we do not want to read
1930     // data into it - we should instead put it into the default state, so it's
1931     // ready for us if and when we switch back to it.
1932     InitDBStates();
1933 
1934   } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1935     nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
1936     if (prefBranch) PrefChanged(prefBranch);
1937 
1938   } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1939     // Flush all the cookies stored by private browsing contexts
1940     mozilla::OriginAttributesPattern pattern;
1941     pattern.mPrivateBrowsingId.Construct(1);
1942     RemoveCookiesWithOriginAttributes(pattern, EmptyCString());
1943     mPrivateDBState = new DBState();
1944   }
1945 
1946   return NS_OK;
1947 }
1948 
1949 NS_IMETHODIMP
GetCookieString(nsIURI * aHostURI,nsIChannel * aChannel,char ** aCookie)1950 nsCookieService::GetCookieString(nsIURI *aHostURI, nsIChannel *aChannel,
1951                                  char **aCookie) {
1952   return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
1953 }
1954 
1955 NS_IMETHODIMP
GetCookieStringFromHttp(nsIURI * aHostURI,nsIURI * aFirstURI,nsIChannel * aChannel,char ** aCookie)1956 nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, nsIURI *aFirstURI,
1957                                          nsIChannel *aChannel, char **aCookie) {
1958   return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
1959 }
1960 
GetCookieStringCommon(nsIURI * aHostURI,nsIChannel * aChannel,bool aHttpBound,char ** aCookie)1961 nsresult nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
1962                                                 nsIChannel *aChannel,
1963                                                 bool aHttpBound,
1964                                                 char **aCookie) {
1965   NS_ENSURE_ARG(aHostURI);
1966   NS_ENSURE_ARG(aCookie);
1967 
1968   // Determine whether the request is foreign. Failure is acceptable.
1969   bool isForeign = true;
1970   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
1971 
1972   // Get originAttributes.
1973   OriginAttributes attrs;
1974   if (aChannel) {
1975     NS_GetOriginAttributes(aChannel, attrs);
1976   }
1977 
1978   bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel);
1979   bool isSameSiteForeign = NS_IsSameSiteForeign(aChannel, aHostURI);
1980   nsAutoCString result;
1981   GetCookieStringInternal(aHostURI, isForeign, isSafeTopLevelNav,
1982                           isSameSiteForeign, aHttpBound, attrs, result);
1983   *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
1984   return NS_OK;
1985 }
1986 
1987 NS_IMETHODIMP
SetCookieString(nsIURI * aHostURI,nsIPrompt * aPrompt,const char * aCookieHeader,nsIChannel * aChannel)1988 nsCookieService::SetCookieString(nsIURI *aHostURI, nsIPrompt *aPrompt,
1989                                  const char *aCookieHeader,
1990                                  nsIChannel *aChannel) {
1991   // The aPrompt argument is deprecated and unused.  Avoid introducing new
1992   // code that uses this argument by warning if the value is non-null.
1993   MOZ_ASSERT(!aPrompt);
1994   if (aPrompt) {
1995     nsCOMPtr<nsIConsoleService> aConsoleService =
1996         do_GetService("@mozilla.org/consoleservice;1");
1997     if (aConsoleService) {
1998       aConsoleService->LogStringMessage(
1999           u"Non-null prompt ignored by nsCookieService.");
2000     }
2001   }
2002   return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
2003                                false);
2004 }
2005 
2006 NS_IMETHODIMP
SetCookieStringFromHttp(nsIURI * aHostURI,nsIURI * aFirstURI,nsIPrompt * aPrompt,const char * aCookieHeader,const char * aServerTime,nsIChannel * aChannel)2007 nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI, nsIURI *aFirstURI,
2008                                          nsIPrompt *aPrompt,
2009                                          const char *aCookieHeader,
2010                                          const char *aServerTime,
2011                                          nsIChannel *aChannel) {
2012   // The aPrompt argument is deprecated and unused.  Avoid introducing new
2013   // code that uses this argument by warning if the value is non-null.
2014   MOZ_ASSERT(!aPrompt);
2015   if (aPrompt) {
2016     nsCOMPtr<nsIConsoleService> aConsoleService =
2017         do_GetService("@mozilla.org/consoleservice;1");
2018     if (aConsoleService) {
2019       aConsoleService->LogStringMessage(
2020           u"Non-null prompt ignored by nsCookieService.");
2021     }
2022   }
2023   return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
2024                                true);
2025 }
2026 
ParseServerTime(const nsCString & aServerTime)2027 int64_t nsCookieService::ParseServerTime(const nsCString &aServerTime) {
2028   // parse server local time. this is not just done here for efficiency
2029   // reasons - if there's an error parsing it, and we need to default it
2030   // to the current time, we must do it here since the current time in
2031   // SetCookieInternal() will change for each cookie processed (e.g. if the
2032   // user is prompted).
2033   PRTime tempServerTime;
2034   int64_t serverTime;
2035   PRStatus result =
2036       PR_ParseTimeString(aServerTime.get(), true, &tempServerTime);
2037   if (result == PR_SUCCESS) {
2038     serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
2039   } else {
2040     serverTime = PR_Now() / PR_USEC_PER_SEC;
2041   }
2042 
2043   return serverTime;
2044 }
2045 
SetCookieStringCommon(nsIURI * aHostURI,const char * aCookieHeader,const char * aServerTime,nsIChannel * aChannel,bool aFromHttp)2046 nsresult nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
2047                                                 const char *aCookieHeader,
2048                                                 const char *aServerTime,
2049                                                 nsIChannel *aChannel,
2050                                                 bool aFromHttp) {
2051   NS_ENSURE_ARG(aHostURI);
2052   NS_ENSURE_ARG(aCookieHeader);
2053 
2054   // Determine whether the request is foreign. Failure is acceptable.
2055   bool isForeign = true;
2056   mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
2057 
2058   // Get originAttributes.
2059   OriginAttributes attrs;
2060   if (aChannel) {
2061     NS_GetOriginAttributes(aChannel, attrs);
2062   }
2063 
2064   nsDependentCString cookieString(aCookieHeader);
2065   nsDependentCString serverTime(aServerTime ? aServerTime : "");
2066   SetCookieStringInternal(aHostURI, isForeign, cookieString, serverTime,
2067                           aFromHttp, attrs, aChannel);
2068   return NS_OK;
2069 }
2070 
SetCookieStringInternal(nsIURI * aHostURI,bool aIsForeign,nsDependentCString & aCookieHeader,const nsCString & aServerTime,bool aFromHttp,const OriginAttributes & aOriginAttrs,nsIChannel * aChannel)2071 void nsCookieService::SetCookieStringInternal(
2072     nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader,
2073     const nsCString &aServerTime, bool aFromHttp,
2074     const OriginAttributes &aOriginAttrs, nsIChannel *aChannel) {
2075   NS_ASSERTION(aHostURI, "null host!");
2076 
2077   if (!mDBState) {
2078     NS_WARNING("No DBState! Profile already closed?");
2079     return;
2080   }
2081 
2082   EnsureReadComplete(true);
2083 
2084   AutoRestore<DBState *> savePrevDBState(mDBState);
2085   mDBState =
2086       (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2087 
2088   // get the base domain for the host URI.
2089   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2090   // file:// URI's (i.e. with an empty host) are allowed, but any other
2091   // scheme must have a non-empty host. A trailing dot in the host
2092   // is acceptable.
2093   bool requireHostMatch;
2094   nsAutoCString baseDomain;
2095   nsresult rv =
2096       GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch);
2097   if (NS_FAILED(rv)) {
2098     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
2099                       "couldn't get base domain from URI");
2100     return;
2101   }
2102 
2103   nsCookieKey key(baseDomain, aOriginAttrs);
2104 
2105   // check default prefs
2106   uint32_t priorCookieCount = 0;
2107   nsAutoCString hostFromURI;
2108   aHostURI->GetHost(hostFromURI);
2109   CountCookiesFromHost(hostFromURI, &priorCookieCount);
2110   CookieStatus cookieStatus =
2111       CheckPrefs(mPermissionService, mCookieBehavior, mThirdPartySession,
2112                  mThirdPartyNonsecureSession, aHostURI, aIsForeign,
2113                  aCookieHeader.get(), priorCookieCount, aOriginAttrs);
2114 
2115   // fire a notification if third party or if cookie was rejected
2116   // (but not if there was an error)
2117   switch (cookieStatus) {
2118     case STATUS_REJECTED:
2119       NotifyRejected(aHostURI);
2120       if (aIsForeign) {
2121         NotifyThirdParty(aHostURI, false, aChannel);
2122       }
2123       return;  // Stop here
2124     case STATUS_REJECTED_WITH_ERROR:
2125       return;
2126     case STATUS_ACCEPTED:  // Fallthrough
2127     case STATUS_ACCEPT_SESSION:
2128       if (aIsForeign) {
2129         NotifyThirdParty(aHostURI, true, aChannel);
2130       }
2131       break;
2132     default:
2133       break;
2134   }
2135 
2136   int64_t serverTime = ParseServerTime(aServerTime);
2137 
2138   // process each cookie in the header
2139   while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
2140                            aCookieHeader, serverTime, aFromHttp, aChannel)) {
2141     // document.cookie can only set one cookie at a time
2142     if (!aFromHttp) break;
2143   }
2144 }
2145 
2146 // notify observers that a cookie was rejected due to the users' prefs.
NotifyRejected(nsIURI * aHostURI)2147 void nsCookieService::NotifyRejected(nsIURI *aHostURI) {
2148   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2149   if (os) {
2150     os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
2151   }
2152 }
2153 
2154 // notify observers that a third-party cookie was accepted/rejected
2155 // if the cookie issuer is unknown, it defaults to "?"
NotifyThirdParty(nsIURI * aHostURI,bool aIsAccepted,nsIChannel * aChannel)2156 void nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted,
2157                                        nsIChannel *aChannel) {
2158   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2159   if (!os) {
2160     return;
2161   }
2162 
2163   const char *topic;
2164 
2165   if (mDBState != mPrivateDBState) {
2166     // Regular (non-private) browsing
2167     if (aIsAccepted) {
2168       topic = "third-party-cookie-accepted";
2169     } else {
2170       topic = "third-party-cookie-rejected";
2171     }
2172   } else {
2173     // Private browsing
2174     if (aIsAccepted) {
2175       topic = "private-third-party-cookie-accepted";
2176     } else {
2177       topic = "private-third-party-cookie-rejected";
2178     }
2179   }
2180 
2181   do {
2182     // Attempt to find the host of aChannel.
2183     if (!aChannel) {
2184       break;
2185     }
2186     nsCOMPtr<nsIURI> channelURI;
2187     nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
2188     if (NS_FAILED(rv)) {
2189       break;
2190     }
2191 
2192     nsAutoCString referringHost;
2193     rv = channelURI->GetHost(referringHost);
2194     if (NS_FAILED(rv)) {
2195       break;
2196     }
2197 
2198     nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
2199     os->NotifyObservers(aHostURI, topic, referringHostUTF16.get());
2200     return;
2201   } while (false);
2202 
2203   // This can fail for a number of reasons, in which kind we fallback to "?"
2204   os->NotifyObservers(aHostURI, topic, u"?");
2205 }
2206 
2207 // notify observers that the cookie list changed. there are five possible
2208 // values for aData:
2209 // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
2210 // "added"   means a cookie was added. aSubject is the added cookie.
2211 // "changed" means a cookie was altered. aSubject is the new cookie.
2212 // "cleared" means the entire cookie list was cleared. aSubject is null.
2213 // "batch-deleted" means a set of cookies was purged. aSubject is the list of
2214 // cookies.
NotifyChanged(nsISupports * aSubject,const char16_t * aData,bool aOldCookieIsSession,bool aFromHttp)2215 void nsCookieService::NotifyChanged(nsISupports *aSubject,
2216                                     const char16_t *aData,
2217                                     bool aOldCookieIsSession, bool aFromHttp) {
2218   const char *topic =
2219       mDBState == mPrivateDBState ? "private-cookie-changed" : "cookie-changed";
2220   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2221   if (!os) {
2222     return;
2223   }
2224   // Notify for topic "private-cookie-changed" or "cookie-changed"
2225   os->NotifyObservers(aSubject, topic, aData);
2226 
2227   // Notify for topic "session-cookie-changed" to update the copy of session
2228   // cookies in session restore component.
2229   // Ignore private session cookies since they will not be restored.
2230   if (mDBState == mPrivateDBState) {
2231     return;
2232   }
2233   // Filter out notifications for individual non-session cookies.
2234   if (NS_LITERAL_STRING("changed").Equals(aData) ||
2235       NS_LITERAL_STRING("deleted").Equals(aData) ||
2236       NS_LITERAL_STRING("added").Equals(aData)) {
2237     nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
2238     MOZ_ASSERT(xpcCookie);
2239     auto cookie = static_cast<nsCookie *>(xpcCookie.get());
2240     if (!cookie->IsSession() && !aOldCookieIsSession) {
2241       return;
2242     }
2243   }
2244   os->NotifyObservers(aSubject, "session-cookie-changed", aData);
2245 }
2246 
CreatePurgeList(nsICookie2 * aCookie)2247 already_AddRefed<nsIArray> nsCookieService::CreatePurgeList(
2248     nsICookie2 *aCookie) {
2249   nsCOMPtr<nsIMutableArray> removedList =
2250       do_CreateInstance(NS_ARRAY_CONTRACTID);
2251   removedList->AppendElement(aCookie);
2252   return removedList.forget();
2253 }
2254 
2255 /******************************************************************************
2256  * nsCookieService:
2257  * public transaction helper impl
2258  ******************************************************************************/
2259 
2260 NS_IMETHODIMP
RunInTransaction(nsICookieTransactionCallback * aCallback)2261 nsCookieService::RunInTransaction(nsICookieTransactionCallback *aCallback) {
2262   NS_ENSURE_ARG(aCallback);
2263   if (!mDBState) {
2264     NS_WARNING("No DBState! Profile already closed?");
2265     return NS_ERROR_NOT_AVAILABLE;
2266   }
2267 
2268   EnsureReadComplete(true);
2269 
2270   if (NS_WARN_IF(!mDefaultDBState->dbConn)) {
2271     return NS_ERROR_NOT_AVAILABLE;
2272   }
2273   mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
2274 
2275   if (NS_FAILED(aCallback->Callback())) {
2276     Unused << transaction.Rollback();
2277     return NS_ERROR_FAILURE;
2278   }
2279   return NS_OK;
2280 }
2281 
2282 /******************************************************************************
2283  * nsCookieService:
2284  * pref observer impl
2285  ******************************************************************************/
2286 
PrefChanged(nsIPrefBranch * aPrefBranch)2287 void nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch) {
2288   int32_t val;
2289   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
2290     mCookieBehavior = (uint8_t)LIMIT(val, 0, 3, 0);
2291 
2292   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
2293     mMaxNumberOfCookies = (uint16_t)LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
2294 
2295   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
2296     mMaxCookiesPerHost = (uint16_t)LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
2297 
2298   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
2299     mCookiePurgeAge =
2300         int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
2301   }
2302 
2303   bool boolval;
2304   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
2305     mThirdPartySession = boolval;
2306 
2307   if (NS_SUCCEEDED(
2308           aPrefBranch->GetBoolPref(kPrefThirdPartyNonsecureSession, &boolval)))
2309     mThirdPartyNonsecureSession = boolval;
2310 
2311   if (NS_SUCCEEDED(
2312           aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
2313     mLeaveSecureAlone = boolval;
2314 }
2315 
2316 /******************************************************************************
2317  * nsICookieManager impl:
2318  * nsICookieManager
2319  ******************************************************************************/
2320 
2321 NS_IMETHODIMP
RemoveAll()2322 nsCookieService::RemoveAll() {
2323   if (!mDBState) {
2324     NS_WARNING("No DBState! Profile already closed?");
2325     return NS_ERROR_NOT_AVAILABLE;
2326   }
2327 
2328   EnsureReadComplete(true);
2329 
2330   RemoveAllFromMemory();
2331 
2332   // clear the cookie file
2333   if (mDBState->dbConn) {
2334     NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
2335 
2336     nsCOMPtr<mozIStorageAsyncStatement> stmt;
2337     nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(
2338         NS_LITERAL_CSTRING("DELETE FROM moz_cookies"), getter_AddRefs(stmt));
2339     if (NS_SUCCEEDED(rv)) {
2340       nsCOMPtr<mozIStoragePendingStatement> handle;
2341       rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
2342                               getter_AddRefs(handle));
2343       NS_ASSERT_SUCCESS(rv);
2344     } else {
2345       // Recreate the database.
2346       COOKIE_LOGSTRING(LogLevel::Debug,
2347                        ("RemoveAll(): corruption detected with rv 0x%" PRIx32,
2348                         static_cast<uint32_t>(rv)));
2349       HandleCorruptDB(mDefaultDBState);
2350     }
2351   }
2352 
2353   NotifyChanged(nullptr, u"cleared");
2354   return NS_OK;
2355 }
2356 
2357 NS_IMETHODIMP
GetEnumerator(nsISimpleEnumerator ** aEnumerator)2358 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator) {
2359   if (!mDBState) {
2360     NS_WARNING("No DBState! Profile already closed?");
2361     return NS_ERROR_NOT_AVAILABLE;
2362   }
2363 
2364   EnsureReadComplete(true);
2365 
2366   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
2367   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
2368     const nsCookieEntry::ArrayType &cookies = iter.Get()->GetCookies();
2369     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
2370       cookieList.AppendObject(cookies[i]);
2371     }
2372   }
2373 
2374   return NS_NewArrayEnumerator(aEnumerator, cookieList);
2375 }
2376 
2377 NS_IMETHODIMP
GetSessionEnumerator(nsISimpleEnumerator ** aEnumerator)2378 nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator) {
2379   if (!mDBState) {
2380     NS_WARNING("No DBState! Profile already closed?");
2381     return NS_ERROR_NOT_AVAILABLE;
2382   }
2383 
2384   EnsureReadComplete(true);
2385 
2386   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
2387   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
2388     const nsCookieEntry::ArrayType &cookies = iter.Get()->GetCookies();
2389     for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
2390       nsCookie *cookie = cookies[i];
2391       // Filter out non-session cookies.
2392       if (cookie->IsSession()) {
2393         cookieList.AppendObject(cookie);
2394       }
2395     }
2396   }
2397 
2398   return NS_NewArrayEnumerator(aEnumerator, cookieList);
2399 }
2400 
InitializeOriginAttributes(OriginAttributes * aAttrs,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,const char16_t * aAPI,const char16_t * aInterfaceSuffix)2401 static nsresult InitializeOriginAttributes(OriginAttributes *aAttrs,
2402                                            JS::HandleValue aOriginAttributes,
2403                                            JSContext *aCx, uint8_t aArgc,
2404                                            const char16_t *aAPI,
2405                                            const char16_t *aInterfaceSuffix) {
2406   MOZ_ASSERT(aAttrs);
2407   MOZ_ASSERT(aCx);
2408   MOZ_ASSERT(aAPI);
2409   MOZ_ASSERT(aInterfaceSuffix);
2410 
2411   if (aArgc == 0) {
2412     const char16_t *params[] = {aAPI, aInterfaceSuffix};
2413 
2414     // This is supposed to be temporary and in 1 or 2 releases we want to
2415     // have originAttributes param as mandatory. But for now, we don't want to
2416     // break existing addons, so we write a console message to inform the addon
2417     // developers about it.
2418     nsContentUtils::ReportToConsole(
2419         nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Cookie Manager"),
2420         nullptr, nsContentUtils::eNECKO_PROPERTIES,
2421         "nsICookieManagerAPIDeprecated", params, ArrayLength(params));
2422   } else if (aArgc == 1) {
2423     if (!aOriginAttributes.isObject() ||
2424         !aAttrs->Init(aCx, aOriginAttributes)) {
2425       return NS_ERROR_INVALID_ARG;
2426     }
2427   }
2428 
2429   return NS_OK;
2430 }
2431 
2432 NS_IMETHODIMP
Add(const nsACString & aHost,const nsACString & aPath,const nsACString & aName,const nsACString & aValue,bool aIsSecure,bool aIsHttpOnly,bool aIsSession,int64_t aExpiry,JS::HandleValue aOriginAttributes,int32_t aSameSite,JSContext * aCx,uint8_t aArgc)2433 nsCookieService::Add(const nsACString &aHost, const nsACString &aPath,
2434                      const nsACString &aName, const nsACString &aValue,
2435                      bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
2436                      int64_t aExpiry, JS::HandleValue aOriginAttributes,
2437                      int32_t aSameSite, JSContext *aCx, uint8_t aArgc) {
2438   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
2439 
2440   OriginAttributes attrs;
2441   nsresult rv = InitializeOriginAttributes(
2442       &attrs, aOriginAttributes, aCx, aArgc, u"nsICookieManager.add()", u"2");
2443   NS_ENSURE_SUCCESS(rv, rv);
2444 
2445   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
2446                    aIsSession, aExpiry, &attrs, aSameSite);
2447 }
2448 
NS_IMETHODIMP_(nsresult)2449 NS_IMETHODIMP_(nsresult)
2450 nsCookieService::AddNative(const nsACString &aHost, const nsACString &aPath,
2451                            const nsACString &aName, const nsACString &aValue,
2452                            bool aIsSecure, bool aIsHttpOnly, bool aIsSession,
2453                            int64_t aExpiry, OriginAttributes *aOriginAttributes,
2454                            int32_t aSameSite) {
2455   if (NS_WARN_IF(!aOriginAttributes)) {
2456     return NS_ERROR_FAILURE;
2457   }
2458 
2459   if (!mDBState) {
2460     NS_WARNING("No DBState! Profile already closed?");
2461     return NS_ERROR_NOT_AVAILABLE;
2462   }
2463 
2464   EnsureReadComplete(true);
2465 
2466   AutoRestore<DBState *> savePrevDBState(mDBState);
2467   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState
2468                                                          : mDefaultDBState;
2469 
2470   // first, normalize the hostname, and fail if it contains illegal characters.
2471   nsAutoCString host(aHost);
2472   nsresult rv = NormalizeHost(host);
2473   NS_ENSURE_SUCCESS(rv, rv);
2474 
2475   // get the base domain for the host URI.
2476   // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
2477   nsAutoCString baseDomain;
2478   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
2479   NS_ENSURE_SUCCESS(rv, rv);
2480 
2481   int64_t currentTimeInUsec = PR_Now();
2482   nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes);
2483 
2484   RefPtr<nsCookie> cookie = nsCookie::Create(
2485       aName, aValue, host, aPath, aExpiry, currentTimeInUsec,
2486       nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), aIsSession,
2487       aIsSecure, aIsHttpOnly, key.mOriginAttributes, aSameSite);
2488   if (!cookie) {
2489     return NS_ERROR_OUT_OF_MEMORY;
2490   }
2491 
2492   AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
2493   return NS_OK;
2494 }
2495 
Remove(const nsACString & aHost,const OriginAttributes & aAttrs,const nsACString & aName,const nsACString & aPath,bool aBlocked)2496 nsresult nsCookieService::Remove(const nsACString &aHost,
2497                                  const OriginAttributes &aAttrs,
2498                                  const nsACString &aName,
2499                                  const nsACString &aPath, bool aBlocked) {
2500   if (!mDBState) {
2501     NS_WARNING("No DBState! Profile already closed?");
2502     return NS_ERROR_NOT_AVAILABLE;
2503   }
2504 
2505   EnsureReadComplete(true);
2506 
2507   AutoRestore<DBState *> savePrevDBState(mDBState);
2508   mDBState =
2509       (aAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2510 
2511   // first, normalize the hostname, and fail if it contains illegal characters.
2512   nsAutoCString host(aHost);
2513   nsresult rv = NormalizeHost(host);
2514   NS_ENSURE_SUCCESS(rv, rv);
2515 
2516   nsAutoCString baseDomain;
2517   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
2518   NS_ENSURE_SUCCESS(rv, rv);
2519 
2520   nsListIter matchIter;
2521   RefPtr<nsCookie> cookie;
2522   if (FindCookie(nsCookieKey(baseDomain, aAttrs), host,
2523                  PromiseFlatCString(aName), PromiseFlatCString(aPath),
2524                  matchIter)) {
2525     cookie = matchIter.Cookie();
2526     RemoveCookieFromList(matchIter);
2527   }
2528 
2529   // check if we need to add the host to the permissions blacklist.
2530   if (aBlocked && mPermissionService) {
2531     // strip off the domain dot, if necessary
2532     if (!host.IsEmpty() && host.First() == '.') host.Cut(0, 1);
2533 
2534     host.InsertLiteral("http://", 0);
2535 
2536     nsCOMPtr<nsIURI> uri;
2537     NS_NewURI(getter_AddRefs(uri), host);
2538 
2539     if (uri)
2540       mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
2541   }
2542 
2543   if (cookie) {
2544     // Everything's done. Notify observers.
2545     NotifyChanged(cookie, u"deleted");
2546   }
2547 
2548   return NS_OK;
2549 }
2550 
2551 NS_IMETHODIMP
Remove(const nsACString & aHost,const nsACString & aName,const nsACString & aPath,bool aBlocked,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc)2552 nsCookieService::Remove(const nsACString &aHost, const nsACString &aName,
2553                         const nsACString &aPath, bool aBlocked,
2554                         JS::HandleValue aOriginAttributes, JSContext *aCx,
2555                         uint8_t aArgc) {
2556   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
2557 
2558   OriginAttributes attrs;
2559   nsresult rv = InitializeOriginAttributes(
2560       &attrs, aOriginAttributes, aCx, aArgc, u"nsICookieManager.remove()", u"");
2561   NS_ENSURE_SUCCESS(rv, rv);
2562 
2563   return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
2564 }
2565 
NS_IMETHODIMP_(nsresult)2566 NS_IMETHODIMP_(nsresult)
2567 nsCookieService::RemoveNative(const nsACString &aHost, const nsACString &aName,
2568                               const nsACString &aPath, bool aBlocked,
2569                               OriginAttributes *aOriginAttributes) {
2570   if (NS_WARN_IF(!aOriginAttributes)) {
2571     return NS_ERROR_FAILURE;
2572   }
2573 
2574   nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked);
2575   if (NS_WARN_IF(NS_FAILED(rv))) {
2576     return rv;
2577   }
2578 
2579   return NS_OK;
2580 }
2581 
2582 /******************************************************************************
2583  * nsCookieService impl:
2584  * private file I/O functions
2585  ******************************************************************************/
2586 
2587 // Extract data from a single result row and create an nsCookie.
GetCookieFromRow(mozIStorageStatement * aRow,const OriginAttributes & aOriginAttributes)2588 mozilla::UniquePtr<ConstCookie> nsCookieService::GetCookieFromRow(
2589     mozIStorageStatement *aRow, const OriginAttributes &aOriginAttributes) {
2590   // Skip reading 'baseDomain' -- up to the caller.
2591   nsCString name, value, host, path;
2592   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
2593   NS_ASSERT_SUCCESS(rv);
2594   rv = aRow->GetUTF8String(IDX_VALUE, value);
2595   NS_ASSERT_SUCCESS(rv);
2596   rv = aRow->GetUTF8String(IDX_HOST, host);
2597   NS_ASSERT_SUCCESS(rv);
2598   rv = aRow->GetUTF8String(IDX_PATH, path);
2599   NS_ASSERT_SUCCESS(rv);
2600 
2601   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
2602   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
2603   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
2604   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
2605   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
2606   int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
2607 
2608   // Create a new constCookie and assign the data.
2609   return mozilla::MakeUnique<ConstCookie>(
2610       name, value, host, path, expiry, lastAccessed, creationTime, isSecure,
2611       isHttpOnly, aOriginAttributes, sameSite);
2612 }
2613 
EnsureReadComplete(bool aInitDBConn)2614 void nsCookieService::EnsureReadComplete(bool aInitDBConn) {
2615   MOZ_ASSERT(NS_IsMainThread());
2616 
2617   bool isAccumulated = false;
2618 
2619   if (!mInitializedDBStates) {
2620     TimeStamp startBlockTime = TimeStamp::Now();
2621     MonitorAutoLock lock(mMonitor);
2622 
2623     while (!mInitializedDBStates) {
2624       mMonitor.Wait();
2625     }
2626     Telemetry::AccumulateTimeDelta(
2627         Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, startBlockTime);
2628     Telemetry::Accumulate(
2629         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
2630     isAccumulated = true;
2631   } else if (!mEndInitDBConn.IsNull()) {
2632     // We didn't block main thread, and here comes the first cookie request.
2633     // Collect how close we're going to block main thread.
2634     Telemetry::Accumulate(
2635         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS,
2636         (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds());
2637     // Nullify the timestamp so wo don't accumulate this telemetry probe again.
2638     mEndInitDBConn = TimeStamp();
2639     isAccumulated = true;
2640   } else if (!mInitializedDBConn && aInitDBConn) {
2641     // A request comes while we finished cookie thread task and InitDBConn is
2642     // on the way from cookie thread to main thread. We're very close to block
2643     // main thread.
2644     Telemetry::Accumulate(
2645         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
2646     isAccumulated = true;
2647   }
2648 
2649   if (!mInitializedDBConn && aInitDBConn && mDefaultDBState) {
2650     InitDBConn();
2651     if (isAccumulated) {
2652       // Nullify the timestamp so wo don't accumulate this telemetry probe
2653       // again.
2654       mEndInitDBConn = TimeStamp();
2655     }
2656   }
2657 }
2658 
Read()2659 OpenDBResult nsCookieService::Read() {
2660   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
2661 
2662   // Set up a statement to delete any rows with a nullptr 'baseDomain'
2663   // column. This takes care of any cookies set by browsers that don't
2664   // understand the 'baseDomain' column, where the database schema version
2665   // is from one that does. (This would occur when downgrading.)
2666   nsresult rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(
2667       NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE baseDomain ISNULL"));
2668   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
2669 
2670   // Read in the data synchronously.
2671   // see IDX_NAME, etc. for parameter indexes
2672   nsCOMPtr<mozIStorageStatement> stmt;
2673   rv = mDefaultDBState->syncConn->CreateStatement(
2674       NS_LITERAL_CSTRING("SELECT "
2675                          "name, "
2676                          "value, "
2677                          "host, "
2678                          "path, "
2679                          "expiry, "
2680                          "lastAccessed, "
2681                          "creationTime, "
2682                          "isSecure, "
2683                          "isHttpOnly, "
2684                          "baseDomain, "
2685                          "originAttributes, "
2686                          "sameSite "
2687                          "FROM moz_cookies "
2688                          "WHERE baseDomain NOTNULL"),
2689       getter_AddRefs(stmt));
2690 
2691   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
2692 
2693   if (NS_WARN_IF(!mReadArray.IsEmpty())) {
2694     mReadArray.Clear();
2695   }
2696   mReadArray.SetCapacity(kMaxNumberOfCookies);
2697 
2698   nsCString baseDomain, name, value, host, path;
2699   bool hasResult;
2700   while (true) {
2701     rv = stmt->ExecuteStep(&hasResult);
2702     if (NS_WARN_IF(NS_FAILED(rv))) {
2703       mReadArray.Clear();
2704       return RESULT_RETRY;
2705     }
2706 
2707     if (!hasResult) break;
2708 
2709     // Make sure we haven't already read the data.
2710     stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
2711 
2712     nsAutoCString suffix;
2713     OriginAttributes attrs;
2714     stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
2715     // If PopulateFromSuffix failed we just ignore the OA attributes
2716     // that we don't support
2717     Unused << attrs.PopulateFromSuffix(suffix);
2718 
2719     nsCookieKey key(baseDomain, attrs);
2720     CookieDomainTuple *tuple = mReadArray.AppendElement();
2721     tuple->key = key;
2722     tuple->cookie = GetCookieFromRow(stmt, attrs);
2723   }
2724 
2725   COOKIE_LOGSTRING(LogLevel::Debug,
2726                    ("Read(): %zu cookies read", mReadArray.Length()));
2727 
2728   return RESULT_OK;
2729 }
2730 
2731 NS_IMETHODIMP
ImportCookies(nsIFile * aCookieFile)2732 nsCookieService::ImportCookies(nsIFile *aCookieFile) {
2733   if (!mDBState) {
2734     NS_WARNING("No DBState! Profile already closed?");
2735     return NS_ERROR_NOT_AVAILABLE;
2736   }
2737 
2738   EnsureReadComplete(true);
2739 
2740   // Make sure we're in the default DB state. We don't want people importing
2741   // cookies into a private browsing session!
2742   if (mDBState != mDefaultDBState) {
2743     NS_WARNING("Trying to import cookies in a private browsing session!");
2744     return NS_ERROR_NOT_AVAILABLE;
2745   }
2746 
2747   nsresult rv;
2748   nsCOMPtr<nsIInputStream> fileInputStream;
2749   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
2750   if (NS_FAILED(rv)) return rv;
2751 
2752   nsCOMPtr<nsILineInputStream> lineInputStream =
2753       do_QueryInterface(fileInputStream, &rv);
2754   if (NS_FAILED(rv)) return rv;
2755 
2756   static const char kTrue[] = "TRUE";
2757 
2758   nsAutoCString buffer, baseDomain;
2759   bool isMore = true;
2760   int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex,
2761       nameIndex, cookieIndex;
2762   nsACString::char_iterator iter;
2763   int32_t numInts;
2764   int64_t expires;
2765   bool isDomain, isHttpOnly = false;
2766   uint32_t originalCookieCount = mDefaultDBState->cookieCount;
2767 
2768   int64_t currentTimeInUsec = PR_Now();
2769   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
2770   // we use lastAccessedCounter to keep cookies in recently-used order,
2771   // so we start by initializing to currentTime (somewhat arbitrary)
2772   int64_t lastAccessedCounter = currentTimeInUsec;
2773 
2774   /* file format is:
2775    *
2776    * host \t isDomain \t path \t secure \t expires \t name \t cookie
2777    *
2778    * if this format isn't respected we move onto the next line in the file.
2779    * isDomain is "TRUE" or "FALSE" (default to "FALSE")
2780    * isSecure is "TRUE" or "FALSE" (default to "TRUE")
2781    * expires is a int64_t integer
2782    * note 1: cookie can contain tabs.
2783    * note 2: cookies will be stored in order of lastAccessed time:
2784    *         most-recently used come first; least-recently-used come last.
2785    */
2786 
2787   /*
2788    * ...but due to bug 178933, we hide HttpOnly cookies from older code
2789    * in a comment, so they don't expose HttpOnly cookies to JS.
2790    *
2791    * The format for HttpOnly cookies is
2792    *
2793    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
2794    *
2795    */
2796 
2797   // We will likely be adding a bunch of cookies to the DB, so we use async
2798   // batching with storage to make this super fast.
2799   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
2800   if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
2801     mDefaultDBState->stmtInsert->NewBindingParamsArray(
2802         getter_AddRefs(paramsArray));
2803   }
2804 
2805   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
2806     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) {
2807       isHttpOnly = true;
2808       hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1;
2809     } else if (buffer.IsEmpty() || buffer.First() == '#') {
2810       continue;
2811     } else {
2812       isHttpOnly = false;
2813       hostIndex = 0;
2814     }
2815 
2816     // this is a cheap, cheesy way of parsing a tab-delimited line into
2817     // string indexes, which can be lopped off into substrings. just for
2818     // purposes of obfuscation, it also checks that each token was found.
2819     // todo: use iterators?
2820     if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
2821         (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
2822         (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
2823         (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
2824         (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
2825         (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
2826       continue;
2827     }
2828 
2829     // check the expirytime first - if it's expired, ignore
2830     // nullstomp the trailing tab, to avoid copying the string
2831     buffer.BeginWriting(iter);
2832     *(iter += nameIndex - 1) = char(0);
2833     numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
2834     if (numInts != 1 || expires < currentTime) {
2835       continue;
2836     }
2837 
2838     isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1)
2839                    .EqualsLiteral(kTrue);
2840     const nsACString &host =
2841         Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
2842     // check for bad legacy cookies (domain not starting with a dot, or
2843     // containing a port), and discard
2844     if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
2845         host.Contains(':')) {
2846       continue;
2847     }
2848 
2849     // compute the baseDomain from the host
2850     rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
2851     if (NS_FAILED(rv)) continue;
2852 
2853     // pre-existing cookies have inIsolatedMozBrowser=false set by default
2854     // constructor of OriginAttributes().
2855     nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
2856 
2857     // Create a new nsCookie and assign the data. We don't know the cookie
2858     // creation time, so just use the current time to generate a unique one.
2859     RefPtr<nsCookie> newCookie = nsCookie::Create(
2860         Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
2861         Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), host,
2862         Substring(buffer, pathIndex, secureIndex - pathIndex - 1), expires,
2863         lastAccessedCounter,
2864         nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), false,
2865         Substring(buffer, secureIndex, expiresIndex - secureIndex - 1)
2866             .EqualsLiteral(kTrue),
2867         isHttpOnly, key.mOriginAttributes, nsICookie2::SAMESITE_UNSET);
2868     if (!newCookie) {
2869       return NS_ERROR_OUT_OF_MEMORY;
2870     }
2871 
2872     // trick: preserve the most-recently-used cookie ordering,
2873     // by successively decrementing the lastAccessed time
2874     lastAccessedCounter--;
2875 
2876     if (originalCookieCount == 0) {
2877       AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
2878     } else {
2879       AddInternal(key, newCookie, currentTimeInUsec, nullptr, nullptr, true);
2880     }
2881   }
2882 
2883   // If we need to write to disk, do so now.
2884   if (paramsArray) {
2885     uint32_t length;
2886     paramsArray->GetLength(&length);
2887     if (length) {
2888       rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
2889       NS_ASSERT_SUCCESS(rv);
2890       nsCOMPtr<mozIStoragePendingStatement> handle;
2891       rv = mDefaultDBState->stmtInsert->ExecuteAsync(
2892           mDefaultDBState->insertListener, getter_AddRefs(handle));
2893       NS_ASSERT_SUCCESS(rv);
2894     }
2895   }
2896 
2897   if (mDefaultDBState->cookieCount - originalCookieCount > 0) {
2898     Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_OLD_SCHEMA, 0);
2899   }
2900 
2901   COOKIE_LOGSTRING(LogLevel::Debug,
2902                    ("ImportCookies(): %" PRIu32 " cookies imported",
2903                     mDefaultDBState->cookieCount));
2904 
2905   return NS_OK;
2906 }
2907 
2908 /******************************************************************************
2909  * nsCookieService impl:
2910  * private GetCookie/SetCookie helpers
2911  ******************************************************************************/
2912 
2913 // helper function for GetCookieList
ispathdelimiter(char c)2914 static inline bool ispathdelimiter(char c) {
2915   return c == '/' || c == '?' || c == '#' || c == ';';
2916 }
2917 
DomainMatches(nsCookie * aCookie,const nsACString & aHost)2918 bool nsCookieService::DomainMatches(nsCookie *aCookie,
2919                                     const nsACString &aHost) {
2920   // first, check for an exact host or domain cookie match, e.g. "google.com"
2921   // or ".google.com"; second a subdomain match, e.g.
2922   // host = "mail.google.com", cookie domain = ".google.com".
2923   return aCookie->RawHost() == aHost ||
2924          (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
2925 }
2926 
IsSameSiteEnabled()2927 bool nsCookieService::IsSameSiteEnabled() {
2928   static bool prefInitialized = false;
2929   if (!prefInitialized) {
2930     Preferences::AddBoolVarCache(&sSameSiteEnabled,
2931                                  "network.cookie.same-site.enabled", false);
2932     prefInitialized = true;
2933   }
2934   return sSameSiteEnabled;
2935 }
2936 
PathMatches(nsCookie * aCookie,const nsACString & aPath)2937 bool nsCookieService::PathMatches(nsCookie *aCookie, const nsACString &aPath) {
2938   // calculate cookie path length, excluding trailing '/'
2939   uint32_t cookiePathLen = aCookie->Path().Length();
2940   if (cookiePathLen > 0 && aCookie->Path().Last() == '/') --cookiePathLen;
2941 
2942   // if the given path is shorter than the cookie path, it doesn't match
2943   // if the given path doesn't start with the cookie path, it doesn't match.
2944   if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
2945     return false;
2946 
2947   // if the given path is longer than the cookie path, and the first char after
2948   // the cookie path is not a path delimiter, it doesn't match.
2949   if (aPath.Length() > cookiePathLen &&
2950       !ispathdelimiter(aPath.CharAt(cookiePathLen))) {
2951     /*
2952      * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
2953      * '/' is the "standard" case; the '?' test allows a site at host/abc?def
2954      * to receive a cookie that has a path attribute of abc.  this seems
2955      * strange but at least one major site (citibank, bug 156725) depends
2956      * on it.  The test for # and ; are put in to proactively avoid problems
2957      * with other sites - these are the only other chars allowed in the path.
2958      */
2959     return false;
2960   }
2961 
2962   // either the paths match exactly, or the cookie path is a prefix of
2963   // the given path.
2964   return true;
2965 }
2966 
GetCookiesForURI(nsIURI * aHostURI,bool aIsForeign,bool aIsSafeTopLevelNav,bool aIsSameSiteForeign,bool aHttpBound,const OriginAttributes & aOriginAttrs,nsTArray<nsCookie * > & aCookieList)2967 void nsCookieService::GetCookiesForURI(nsIURI *aHostURI, bool aIsForeign,
2968                                        bool aIsSafeTopLevelNav,
2969                                        bool aIsSameSiteForeign, bool aHttpBound,
2970                                        const OriginAttributes &aOriginAttrs,
2971                                        nsTArray<nsCookie *> &aCookieList) {
2972   NS_ASSERTION(aHostURI, "null host!");
2973 
2974   if (!mDBState) {
2975     NS_WARNING("No DBState! Profile already closed?");
2976     return;
2977   }
2978 
2979   EnsureReadComplete(true);
2980 
2981   AutoRestore<DBState *> savePrevDBState(mDBState);
2982   mDBState =
2983       (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
2984 
2985   // get the base domain, host, and path from the URI.
2986   // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
2987   // file:// URI's (i.e. with an empty host) are allowed, but any other
2988   // scheme must have a non-empty host. A trailing dot in the host
2989   // is acceptable.
2990   bool requireHostMatch;
2991   nsAutoCString baseDomain, hostFromURI, pathFromURI;
2992   nsresult rv =
2993       GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch);
2994   if (NS_SUCCEEDED(rv)) rv = aHostURI->GetAsciiHost(hostFromURI);
2995   if (NS_SUCCEEDED(rv)) rv = aHostURI->GetPathQueryRef(pathFromURI);
2996   if (NS_FAILED(rv)) {
2997     COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr,
2998                       "invalid host/path from URI");
2999     return;
3000   }
3001 
3002   // check default prefs
3003   uint32_t priorCookieCount = 0;
3004   CountCookiesFromHost(hostFromURI, &priorCookieCount);
3005   CookieStatus cookieStatus =
3006       CheckPrefs(mPermissionService, mCookieBehavior, mThirdPartySession,
3007                  mThirdPartyNonsecureSession, aHostURI, aIsForeign, nullptr,
3008                  priorCookieCount, aOriginAttrs);
3009 
3010   // for GetCookie(), we don't fire rejection notifications.
3011   switch (cookieStatus) {
3012     case STATUS_REJECTED:
3013     case STATUS_REJECTED_WITH_ERROR:
3014       return;
3015     default:
3016       break;
3017   }
3018 
3019   // Note: The following permissions logic is mirrored in
3020   // extensions::MatchPattern::MatchesCookie.
3021   // If it changes, please update that function, or file a bug for someone
3022   // else to do so.
3023 
3024   // check if aHostURI is using an https secure protocol.
3025   // if it isn't, then we can't send a secure cookie over the connection.
3026   // if SchemeIs fails, assume an insecure connection, to be on the safe side
3027   bool isSecure;
3028   if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
3029     isSecure = false;
3030   }
3031 
3032   nsCookie *cookie;
3033   int64_t currentTimeInUsec = PR_Now();
3034   int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
3035   bool stale = false;
3036 
3037   nsCookieKey key(baseDomain, aOriginAttrs);
3038 
3039   // perform the hash lookup
3040   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
3041   if (!entry) return;
3042 
3043   // iterate the cookies!
3044   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
3045   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
3046     cookie = cookies[i];
3047 
3048     // check the host, since the base domain lookup is conservative.
3049     if (!DomainMatches(cookie, hostFromURI)) continue;
3050 
3051     // if the cookie is secure and the host scheme isn't, we can't send it
3052     if (cookie->IsSecure() && !isSecure) continue;
3053 
3054     int32_t sameSiteAttr = 0;
3055     cookie->GetSameSite(&sameSiteAttr);
3056     if (aIsSameSiteForeign && IsSameSiteEnabled()) {
3057       // it if's a cross origin request and the cookie is same site only
3058       // (strict) don't send it
3059       if (sameSiteAttr == nsICookie2::SAMESITE_STRICT) {
3060         continue;
3061       }
3062       // if it's a cross origin request, the cookie is same site lax, but it's
3063       // not a top-level navigation, don't send it
3064       if (sameSiteAttr == nsICookie2::SAMESITE_LAX && !aIsSafeTopLevelNav) {
3065         continue;
3066       }
3067     }
3068 
3069     // if the cookie is httpOnly and it's not going directly to the HTTP
3070     // connection, don't send it
3071     if (cookie->IsHttpOnly() && !aHttpBound) continue;
3072 
3073     // if the nsIURI path doesn't match the cookie path, don't send it back
3074     if (!PathMatches(cookie, pathFromURI)) continue;
3075 
3076     // check if the cookie has expired
3077     if (cookie->Expiry() <= currentTime) {
3078       continue;
3079     }
3080 
3081     // all checks passed - add to list and check if lastAccessed stamp needs
3082     // updating
3083     aCookieList.AppendElement(cookie);
3084     if (cookie->IsStale()) {
3085       stale = true;
3086     }
3087   }
3088 
3089   int32_t count = aCookieList.Length();
3090   if (count == 0) return;
3091 
3092   // update lastAccessed timestamps. we only do this if the timestamp is stale
3093   // by a certain amount, to avoid thrashing the db during pageload.
3094   if (stale) {
3095     // Create an array of parameters to bind to our update statement. Batching
3096     // is OK here since we're updating cookies with no interleaved operations.
3097     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
3098     mozIStorageAsyncStatement *stmt = mDBState->stmtUpdate;
3099     if (mDBState->dbConn) {
3100       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
3101     }
3102 
3103     for (int32_t i = 0; i < count; ++i) {
3104       cookie = aCookieList.ElementAt(i);
3105 
3106       if (cookie->IsStale()) {
3107         UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
3108       }
3109     }
3110     // Update the database now if necessary.
3111     if (paramsArray) {
3112       uint32_t length;
3113       paramsArray->GetLength(&length);
3114       if (length) {
3115         DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
3116         NS_ASSERT_SUCCESS(rv);
3117         nsCOMPtr<mozIStoragePendingStatement> handle;
3118         rv = stmt->ExecuteAsync(mDBState->updateListener,
3119                                 getter_AddRefs(handle));
3120         NS_ASSERT_SUCCESS(rv);
3121       }
3122     }
3123   }
3124 
3125   // return cookies in order of path length; longest to shortest.
3126   // this is required per RFC2109.  if cookies match in length,
3127   // then sort by creation time (see bug 236772).
3128   aCookieList.Sort(CompareCookiesForSending());
3129 }
3130 
GetCookieStringInternal(nsIURI * aHostURI,bool aIsForeign,bool aIsSafeTopLevelNav,bool aIsSameSiteForeign,bool aHttpBound,const OriginAttributes & aOriginAttrs,nsCString & aCookieString)3131 void nsCookieService::GetCookieStringInternal(
3132     nsIURI *aHostURI, bool aIsForeign, bool aIsSafeTopLevelNav,
3133     bool aIsSameSiteForeign, bool aHttpBound,
3134     const OriginAttributes &aOriginAttrs, nsCString &aCookieString) {
3135   AutoTArray<nsCookie *, 8> foundCookieList;
3136   GetCookiesForURI(aHostURI, aIsForeign, aIsSafeTopLevelNav, aIsSameSiteForeign,
3137                    aHttpBound, aOriginAttrs, foundCookieList);
3138 
3139   nsCookie *cookie;
3140   for (uint32_t i = 0; i < foundCookieList.Length(); ++i) {
3141     cookie = foundCookieList.ElementAt(i);
3142 
3143     // check if we have anything to write
3144     if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
3145       // if we've already added a cookie to the return list, append a "; " so
3146       // that subsequent cookies are delimited in the final list.
3147       if (!aCookieString.IsEmpty()) {
3148         aCookieString.AppendLiteral("; ");
3149       }
3150 
3151       if (!cookie->Name().IsEmpty()) {
3152         // we have a name and value - write both
3153         aCookieString +=
3154             cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
3155       } else {
3156         // just write value
3157         aCookieString += cookie->Value();
3158       }
3159     }
3160   }
3161 
3162   if (!aCookieString.IsEmpty())
3163     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
3164 }
3165 
3166 // processes a single cookie, and returns true if there are more cookies
3167 // to be processed
CanSetCookie(nsIURI * aHostURI,const nsCookieKey & aKey,nsCookieAttributes & aCookieAttributes,bool aRequireHostMatch,CookieStatus aStatus,nsDependentCString & aCookieHeader,int64_t aServerTime,bool aFromHttp,nsIChannel * aChannel,bool aLeaveSecureAlone,bool & aSetCookie,mozIThirdPartyUtil * aThirdPartyUtil)3168 bool nsCookieService::CanSetCookie(nsIURI *aHostURI, const nsCookieKey &aKey,
3169                                    nsCookieAttributes &aCookieAttributes,
3170                                    bool aRequireHostMatch, CookieStatus aStatus,
3171                                    nsDependentCString &aCookieHeader,
3172                                    int64_t aServerTime, bool aFromHttp,
3173                                    nsIChannel *aChannel, bool aLeaveSecureAlone,
3174                                    bool &aSetCookie,
3175                                    mozIThirdPartyUtil *aThirdPartyUtil) {
3176   NS_ASSERTION(aHostURI, "null host!");
3177 
3178   aSetCookie = false;
3179 
3180   // init expiryTime such that session cookies won't prematurely expire
3181   aCookieAttributes.expiryTime = INT64_MAX;
3182 
3183   // aCookieHeader is an in/out param to point to the next cookie, if
3184   // there is one. Save the present value for logging purposes
3185   nsDependentCString savedCookieHeader(aCookieHeader);
3186 
3187   // newCookie says whether there are multiple cookies in the header;
3188   // so we can handle them separately.
3189   bool newCookie = ParseAttributes(aCookieHeader, aCookieAttributes);
3190 
3191   // Collect telemetry on how often secure cookies are set from non-secure
3192   // origins, and vice-versa.
3193   //
3194   // 0 = nonsecure and "http:"
3195   // 1 = nonsecure and "https:"
3196   // 2 = secure and "http:"
3197   // 3 = secure and "https:"
3198   bool isHTTPS;
3199   nsresult rv = aHostURI->SchemeIs("https", &isHTTPS);
3200   if (NS_SUCCEEDED(rv)) {
3201     Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY,
3202                           ((aCookieAttributes.isSecure) ? 0x02 : 0x00) |
3203                               ((isHTTPS) ? 0x01 : 0x00));
3204 
3205     // Collect telemetry on how often are first- and third-party cookies set
3206     // from HTTPS origins:
3207     //
3208     // 0 (000) = first-party and "http:"
3209     // 1 (001) = first-party and "http:" with bogus Secure cookie flag?!
3210     // 2 (010) = first-party and "https:"
3211     // 3 (011) = first-party and "https:" with Secure cookie flag
3212     // 4 (100) = third-party and "http:"
3213     // 5 (101) = third-party and "http:" with bogus Secure cookie flag?!
3214     // 6 (110) = third-party and "https:"
3215     // 7 (111) = third-party and "https:" with Secure cookie flag
3216     if (aThirdPartyUtil) {
3217       bool isThirdParty = true;
3218       aThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isThirdParty);
3219       Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_HTTPS,
3220                             (isThirdParty ? 0x04 : 0x00) |
3221                                 (isHTTPS ? 0x02 : 0x00) |
3222                                 (aCookieAttributes.isSecure ? 0x01 : 0x00));
3223     }
3224   }
3225 
3226   int64_t currentTimeInUsec = PR_Now();
3227 
3228   // calculate expiry time of cookie.
3229   aCookieAttributes.isSession = GetExpiry(aCookieAttributes, aServerTime,
3230                                           currentTimeInUsec / PR_USEC_PER_SEC);
3231   if (aStatus == STATUS_ACCEPT_SESSION) {
3232     // force lifetime to session. note that the expiration time, if set above,
3233     // will still apply.
3234     aCookieAttributes.isSession = true;
3235   }
3236 
3237   // reject cookie if it's over the size limit, per RFC2109
3238   if ((aCookieAttributes.name.Length() + aCookieAttributes.value.Length()) >
3239       kMaxBytesPerCookie) {
3240     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3241                       "cookie too big (> 4kb)");
3242     return newCookie;
3243   }
3244 
3245   const char illegalNameCharacters[] = {
3246       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
3247       0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
3248       0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x00};
3249   if (aCookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) {
3250     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3251                       "invalid name character");
3252     return newCookie;
3253   }
3254 
3255   // domain & path checks
3256   if (!CheckDomain(aCookieAttributes, aHostURI, aKey.mBaseDomain,
3257                    aRequireHostMatch)) {
3258     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3259                       "failed the domain tests");
3260     return newCookie;
3261   }
3262   if (!CheckPath(aCookieAttributes, aHostURI)) {
3263     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3264                       "failed the path tests");
3265     return newCookie;
3266   }
3267   // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
3268   if (!CheckPrefixes(aCookieAttributes, isHTTPS)) {
3269     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3270                       "failed the prefix tests");
3271     return newCookie;
3272   }
3273 
3274   // reject cookie if value contains an RFC 6265 disallowed character - see
3275   // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
3276   // NOTE: this is not the full set of characters disallowed by 6265 - notably
3277   // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is
3278   // for parity with Chrome. This only applies to cookies set via the Set-Cookie
3279   // header, as document.cookie is defined to be UTF-8. Hooray for
3280   // symmetry!</sarcasm>
3281   const char illegalCharacters[] = {
3282       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C,
3283       0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
3284       0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x00};
3285   if (aFromHttp &&
3286       (aCookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
3287     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3288                       "invalid value character");
3289     return newCookie;
3290   }
3291 
3292   // if the new cookie is httponly, make sure we're not coming from script
3293   if (!aFromHttp && aCookieAttributes.isHttpOnly) {
3294     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3295                       "cookie is httponly; coming from script");
3296     return newCookie;
3297   }
3298 
3299   bool isSecure = true;
3300   if (aHostURI) {
3301     aHostURI->SchemeIs("https", &isSecure);
3302   }
3303 
3304   // If the new cookie is non-https and wants to set secure flag,
3305   // browser have to ignore this new cookie.
3306   // (draft-ietf-httpbis-cookie-alone section 3.1)
3307   if (aLeaveSecureAlone && aCookieAttributes.isSecure && !isSecure) {
3308     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3309                       "non-https cookie can't set secure flag");
3310     Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3311                           BLOCKED_SECURE_SET_FROM_HTTP);
3312     return newCookie;
3313   }
3314 
3315   // If the new cookie is same-site but in a cross site context,
3316   // browser must ignore the cookie.
3317   if ((aCookieAttributes.sameSite != nsICookie2::SAMESITE_UNSET) &&
3318       aThirdPartyUtil && IsSameSiteEnabled()) {
3319     // Do not treat loads triggered by web extensions as foreign
3320     bool addonAllowsLoad = false;
3321     if (aChannel) {
3322       nsCOMPtr<nsIURI> channelURI;
3323       NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
3324       nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
3325       addonAllowsLoad =
3326           loadInfo && BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
3327                           ->AddonAllowsLoad(channelURI);
3328     }
3329 
3330     if (!addonAllowsLoad) {
3331       bool isThirdParty = false;
3332       nsresult rv = aThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
3333                                                          &isThirdParty);
3334       if (NS_FAILED(rv) || isThirdParty) {
3335         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3336                           "failed the samesite tests");
3337         return newCookie;
3338       }
3339     }
3340   }
3341 
3342   aSetCookie = true;
3343   return newCookie;
3344 }
3345 
3346 // processes a single cookie, and returns true if there are more cookies
3347 // to be processed
SetCookieInternal(nsIURI * aHostURI,const mozilla::nsCookieKey & aKey,bool aRequireHostMatch,CookieStatus aStatus,nsDependentCString & aCookieHeader,int64_t aServerTime,bool aFromHttp,nsIChannel * aChannel)3348 bool nsCookieService::SetCookieInternal(
3349     nsIURI *aHostURI, const mozilla::nsCookieKey &aKey, bool aRequireHostMatch,
3350     CookieStatus aStatus, nsDependentCString &aCookieHeader,
3351     int64_t aServerTime, bool aFromHttp, nsIChannel *aChannel) {
3352   NS_ASSERTION(aHostURI, "null host!");
3353   bool canSetCookie = false;
3354   nsDependentCString savedCookieHeader(aCookieHeader);
3355   nsCookieAttributes cookieAttributes;
3356   bool newCookie =
3357       CanSetCookie(aHostURI, aKey, cookieAttributes, aRequireHostMatch, aStatus,
3358                    aCookieHeader, aServerTime, aFromHttp, aChannel,
3359                    mLeaveSecureAlone, canSetCookie, mThirdPartyUtil);
3360 
3361   if (!canSetCookie) {
3362     return newCookie;
3363   }
3364 
3365   int64_t currentTimeInUsec = PR_Now();
3366   // create a new nsCookie and copy attributes
3367   RefPtr<nsCookie> cookie = nsCookie::Create(
3368       cookieAttributes.name, cookieAttributes.value, cookieAttributes.host,
3369       cookieAttributes.path, cookieAttributes.expiryTime, currentTimeInUsec,
3370       nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
3371       cookieAttributes.isSession, cookieAttributes.isSecure,
3372       cookieAttributes.isHttpOnly, aKey.mOriginAttributes,
3373       cookieAttributes.sameSite);
3374   if (!cookie) return newCookie;
3375 
3376   // check permissions from site permission list, or ask the user,
3377   // to determine if we can set the cookie
3378   if (mPermissionService) {
3379     bool permission;
3380     mPermissionService->CanSetCookie(
3381         aHostURI, aChannel,
3382         static_cast<nsICookie2 *>(static_cast<nsCookie *>(cookie)),
3383         &cookieAttributes.isSession, &cookieAttributes.expiryTime, &permission);
3384     if (!permission) {
3385       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
3386                         "cookie rejected by permission manager");
3387       NotifyRejected(aHostURI);
3388       return newCookie;
3389     }
3390 
3391     // update isSession and expiry attributes, in case they changed
3392     cookie->SetIsSession(cookieAttributes.isSession);
3393     cookie->SetExpiry(cookieAttributes.expiryTime);
3394   }
3395 
3396   // add the cookie to the list. AddInternal() takes care of logging.
3397   // we get the current time again here, since it may have changed during
3398   // prompting
3399   AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
3400               aFromHttp);
3401   return newCookie;
3402 }
3403 
3404 // this is a backend function for adding a cookie to the list, via SetCookie.
3405 // also used in the cookie manager, for profile migration from IE.
3406 // it either replaces an existing cookie; or adds the cookie to the hashtable,
3407 // and deletes a cookie (if maximum number of cookies has been
3408 // reached). also performs list maintenance by removing expired cookies.
AddInternal(const nsCookieKey & aKey,nsCookie * aCookie,int64_t aCurrentTimeInUsec,nsIURI * aHostURI,const char * aCookieHeader,bool aFromHttp)3409 void nsCookieService::AddInternal(const nsCookieKey &aKey, nsCookie *aCookie,
3410                                   int64_t aCurrentTimeInUsec, nsIURI *aHostURI,
3411                                   const char *aCookieHeader, bool aFromHttp) {
3412   MOZ_ASSERT(mInitializedDBStates);
3413   MOZ_ASSERT(mInitializedDBConn);
3414 
3415   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
3416 
3417   nsListIter exactIter;
3418   bool foundCookie = false;
3419   foundCookie = FindCookie(aKey, aCookie->Host(), aCookie->Name(),
3420                            aCookie->Path(), exactIter);
3421   bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure();
3422   bool isSecure = true;
3423   if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
3424     isSecure = false;
3425   }
3426   bool oldCookieIsSession = false;
3427   if (mLeaveSecureAlone) {
3428     // Step1, call FindSecureCookie(). FindSecureCookie() would
3429     // find the existing cookie with the security flag and has
3430     // the same name, host and path of the new cookie, if there is any.
3431     // Step2, Confirm new cookie's security setting. If any targeted
3432     // cookie had been found in Step1, then confirm whether the
3433     // new cookie could modify it. If the new created cookie’s
3434     // "secure-only-flag" is not set, and the "scheme" component
3435     // of the "request-uri" does not denote a "secure" protocol,
3436     // then ignore the new cookie.
3437     // (draft-ietf-httpbis-cookie-alone section 3.2)
3438     if (!aCookie->IsSecure() &&
3439         (foundSecureExact || FindSecureCookie(aKey, aCookie))) {
3440       if (!isSecure) {
3441         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3442                           "cookie can't save because older cookie is secure "
3443                           "cookie but newer cookie is non-secure cookie");
3444         if (foundSecureExact) {
3445           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3446                                 BLOCKED_DOWNGRADE_SECURE_EXACT);
3447         } else {
3448           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3449                                 BLOCKED_DOWNGRADE_SECURE_INEXACT);
3450         }
3451         return;
3452       }
3453       // A secure site is allowed to downgrade a secure cookie
3454       // but we want to measure anyway.
3455       if (foundSecureExact) {
3456         Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3457                               DOWNGRADE_SECURE_FROM_SECURE_EXACT);
3458       } else {
3459         Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3460                               DOWNGRADE_SECURE_FROM_SECURE_INEXACT);
3461       }
3462     }
3463   }
3464 
3465   RefPtr<nsCookie> oldCookie;
3466   nsCOMPtr<nsIArray> purgedList;
3467   if (foundCookie) {
3468     oldCookie = exactIter.Cookie();
3469     oldCookieIsSession = oldCookie->IsSession();
3470 
3471     // Check if the old cookie is stale (i.e. has already expired). If so, we
3472     // need to be careful about the semantics of removing it and adding the new
3473     // cookie: we want the behavior wrt adding the new cookie to be the same as
3474     // if it didn't exist, but we still want to fire a removal notification.
3475     if (oldCookie->Expiry() <= currentTime) {
3476       if (aCookie->Expiry() <= currentTime) {
3477         // The new cookie has expired and the old one is stale. Nothing to do.
3478         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3479                           "cookie has already expired");
3480         return;
3481       }
3482 
3483       // Remove the stale cookie. We save notification for later, once all list
3484       // modifications are complete.
3485       RemoveCookieFromList(exactIter);
3486       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3487                         "stale cookie was purged");
3488       purgedList = CreatePurgeList(oldCookie);
3489 
3490       // We've done all we need to wrt removing and notifying the stale cookie.
3491       // From here on out, we pretend pretend it didn't exist, so that we
3492       // preserve expected notification semantics when adding the new cookie.
3493       foundCookie = false;
3494 
3495     } else {
3496       // If the old cookie is httponly, make sure we're not coming from script.
3497       if (!aFromHttp && oldCookie->IsHttpOnly()) {
3498         COOKIE_LOGFAILURE(
3499             SET_COOKIE, aHostURI, aCookieHeader,
3500             "previously stored cookie is httponly; coming from script");
3501         return;
3502       }
3503 
3504       // If the new cookie has the same value, expiry date, and isSecure,
3505       // isSession, and isHttpOnly flags then we can just keep the old one.
3506       // Only if any of these differ we would want to override the cookie.
3507       if (oldCookie->Value().Equals(aCookie->Value()) &&
3508           oldCookie->Expiry() == aCookie->Expiry() &&
3509           oldCookie->IsSecure() == aCookie->IsSecure() &&
3510           oldCookie->IsSession() == aCookie->IsSession() &&
3511           oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
3512           // We don't want to perform this optimization if the cookie is
3513           // considered stale, since in this case we would need to update the
3514           // database.
3515           !oldCookie->IsStale()) {
3516         // Update the last access time on the old cookie.
3517         oldCookie->SetLastAccessed(aCookie->LastAccessed());
3518         UpdateCookieOldestTime(mDBState, oldCookie);
3519         return;
3520       }
3521 
3522       // Remove the old cookie.
3523       RemoveCookieFromList(exactIter);
3524 
3525       // If the new cookie has expired -- i.e. the intent was simply to delete
3526       // the old cookie -- then we're done.
3527       if (aCookie->Expiry() <= currentTime) {
3528         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3529                           "previously stored cookie was deleted");
3530         NotifyChanged(oldCookie, u"deleted", oldCookieIsSession, aFromHttp);
3531         return;
3532       }
3533 
3534       // Preserve creation time of cookie for ordering purposes.
3535       aCookie->SetCreationTime(oldCookie->CreationTime());
3536     }
3537 
3538   } else {
3539     // check if cookie has already expired
3540     if (aCookie->Expiry() <= currentTime) {
3541       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
3542                         "cookie has already expired");
3543       return;
3544     }
3545 
3546     // check if we have to delete an old cookie.
3547     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
3548     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
3549       nsListIter iter;
3550       // Prioritize evicting insecure cookies.
3551       // (draft-ietf-httpbis-cookie-alone section 3.3)
3552       mozilla::Maybe<bool> optionalSecurity =
3553           mLeaveSecureAlone ? Some(false) : Nothing();
3554       int64_t oldestCookieTime =
3555           FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
3556       if (iter.entry == nullptr) {
3557         if (aCookie->IsSecure()) {
3558           // It's valid to evict a secure cookie for another secure cookie.
3559           oldestCookieTime =
3560               FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
3561         } else {
3562           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
3563                                 EVICTING_SECURE_BLOCKED);
3564           COOKIE_LOGEVICTED(aCookie,
3565                             "Too many cookies for this domain and the new "
3566                             "cookie is not a secure cookie");
3567           return;
3568         }
3569       }
3570 
3571       MOZ_ASSERT(iter.entry);
3572 
3573       oldCookie = iter.Cookie();
3574       if (oldestCookieTime > 0 && mLeaveSecureAlone) {
3575         TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
3576       }
3577 
3578       // remove the oldest cookie from the domain
3579       RemoveCookieFromList(iter);
3580       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
3581       purgedList = CreatePurgeList(oldCookie);
3582     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
3583       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
3584       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
3585       if (maxAge >= purgeAge) {
3586         // we're over both size and age limits by 10%; time to purge the table!
3587         // do this by:
3588         // 1) removing expired cookies;
3589         // 2) evicting the balance of old cookies until we reach the size limit.
3590         // note that the cookieOldestTime indicator can be pessimistic - if it's
3591         // older than the actual oldest cookie, we'll just purge more eagerly.
3592         purgedList = PurgeCookies(aCurrentTimeInUsec);
3593       }
3594     }
3595   }
3596 
3597   // Add the cookie to the db. We do not supply a params array for batching
3598   // because this might result in removals and additions being out of order.
3599   AddCookieToList(aKey, aCookie, mDBState, nullptr);
3600   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
3601 
3602   // Now that list mutations are complete, notify observers. We do it here
3603   // because observers may themselves attempt to mutate the list.
3604   if (purgedList) {
3605     NotifyChanged(purgedList, u"batch-deleted");
3606   }
3607 
3608   NotifyChanged(aCookie, foundCookie ? u"changed" : u"added",
3609                 oldCookieIsSession, aFromHttp);
3610 }
3611 
3612 /******************************************************************************
3613  * nsCookieService impl:
3614  * private cookie header parsing functions
3615  ******************************************************************************/
3616 
3617 // clang-format off
3618 // The following comment block elucidates the function of ParseAttributes.
3619 /******************************************************************************
3620  ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
3621  ** please note: this BNF deviates from both specifications, and reflects this
3622  ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
3623 
3624  ** Differences from RFC2109/2616 and explanations:
3625     1. implied *LWS
3626          The grammar described by this specification is word-based. Except
3627          where noted otherwise, linear white space (<LWS>) can be included
3628          between any two adjacent words (token or quoted-string), and
3629          between adjacent words and separators, without changing the
3630          interpretation of a field.
3631        <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
3632 
3633     2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
3634        common use inside values.
3635 
3636     3. tokens and values have looser restrictions on allowed characters than
3637        spec. This is also due to certain characters being in common use inside
3638        values. We allow only '=' to separate token/value pairs, and ';' to
3639        terminate tokens or values. <LWS> is allowed within tokens and values
3640        (see bug 206022).
3641 
3642     4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
3643        reject control chars or non-ASCII chars. This is erring on the loose
3644        side, since there's probably no good reason to enforce this strictness.
3645 
3646     5. cookie <NAME> is optional, where spec requires it. This is a fairly
3647        trivial case, but allows the flexibility of setting only a cookie <VALUE>
3648        with a blank <NAME> and is required by some sites (see bug 169091).
3649 
3650     6. Attribute "HttpOnly", not covered in the RFCs, is supported
3651        (see bug 178993).
3652 
3653  ** Begin BNF:
3654     token         = 1*<any allowed-chars except separators>
3655     value         = 1*<any allowed-chars except value-sep>
3656     separators    = ";" | "="
3657     value-sep     = ";"
3658     cookie-sep    = CR | LF
3659     allowed-chars = <any OCTET except NUL or cookie-sep>
3660     OCTET         = <any 8-bit sequence of data>
3661     LWS           = SP | HT
3662     NUL           = <US-ASCII NUL, null control character (0)>
3663     CR            = <US-ASCII CR, carriage return (13)>
3664     LF            = <US-ASCII LF, linefeed (10)>
3665     SP            = <US-ASCII SP, space (32)>
3666     HT            = <US-ASCII HT, horizontal-tab (9)>
3667 
3668     set-cookie    = "Set-Cookie:" cookies
3669     cookies       = cookie *( cookie-sep cookie )
3670     cookie        = [NAME "="] VALUE *(";" cookie-av)    ; cookie NAME/VALUE must come first
3671     NAME          = token                                ; cookie name
3672     VALUE         = value                                ; cookie value
3673     cookie-av     = token ["=" value]
3674 
3675     valid values for cookie-av (checked post-parsing) are:
3676     cookie-av     = "Path"    "=" value
3677                   | "Domain"  "=" value
3678                   | "Expires" "=" value
3679                   | "Max-Age" "=" value
3680                   | "Comment" "=" value
3681                   | "Version" "=" value
3682                   | "Secure"
3683                   | "HttpOnly"
3684 
3685 ******************************************************************************/
3686 // clang-format on
3687 
3688 // helper functions for GetTokenValue
iswhitespace(char c)3689 static inline bool iswhitespace(char c) { return c == ' ' || c == '\t'; }
isterminator(char c)3690 static inline bool isterminator(char c) { return c == '\n' || c == '\r'; }
isvalueseparator(char c)3691 static inline bool isvalueseparator(char c) {
3692   return isterminator(c) || c == ';';
3693 }
istokenseparator(char c)3694 static inline bool istokenseparator(char c) {
3695   return isvalueseparator(c) || c == '=';
3696 }
3697 
3698 // Parse a single token/value pair.
3699 // Returns true if a cookie terminator is found, so caller can parse new cookie.
GetTokenValue(nsACString::const_char_iterator & aIter,nsACString::const_char_iterator & aEndIter,nsDependentCSubstring & aTokenString,nsDependentCSubstring & aTokenValue,bool & aEqualsFound)3700 bool nsCookieService::GetTokenValue(nsACString::const_char_iterator &aIter,
3701                                     nsACString::const_char_iterator &aEndIter,
3702                                     nsDependentCSubstring &aTokenString,
3703                                     nsDependentCSubstring &aTokenValue,
3704                                     bool &aEqualsFound) {
3705   nsACString::const_char_iterator start, lastSpace;
3706   // initialize value string to clear garbage
3707   aTokenValue.Rebind(aIter, aIter);
3708 
3709   // find <token>, including any <LWS> between the end-of-token and the
3710   // token separator. we'll remove trailing <LWS> next
3711   while (aIter != aEndIter && iswhitespace(*aIter)) ++aIter;
3712   start = aIter;
3713   while (aIter != aEndIter && !istokenseparator(*aIter)) ++aIter;
3714 
3715   // remove trailing <LWS>; first check we're not at the beginning
3716   lastSpace = aIter;
3717   if (lastSpace != start) {
3718     while (--lastSpace != start && iswhitespace(*lastSpace)) continue;
3719     ++lastSpace;
3720   }
3721   aTokenString.Rebind(start, lastSpace);
3722 
3723   aEqualsFound = (*aIter == '=');
3724   if (aEqualsFound) {
3725     // find <value>
3726     while (++aIter != aEndIter && iswhitespace(*aIter)) continue;
3727 
3728     start = aIter;
3729 
3730     // process <token>
3731     // just look for ';' to terminate ('=' allowed)
3732     while (aIter != aEndIter && !isvalueseparator(*aIter)) ++aIter;
3733 
3734     // remove trailing <LWS>; first check we're not at the beginning
3735     if (aIter != start) {
3736       lastSpace = aIter;
3737       while (--lastSpace != start && iswhitespace(*lastSpace)) continue;
3738       aTokenValue.Rebind(start, ++lastSpace);
3739     }
3740   }
3741 
3742   // aIter is on ';', or terminator, or EOS
3743   if (aIter != aEndIter) {
3744     // if on terminator, increment past & return true to process new cookie
3745     if (isterminator(*aIter)) {
3746       ++aIter;
3747       return true;
3748     }
3749     // fall-through: aIter is on ';', increment and return false
3750     ++aIter;
3751   }
3752   return false;
3753 }
3754 
3755 // Parses attributes from cookie header. expires/max-age attributes aren't
3756 // folded into the cookie struct here, because we don't know which one to use
3757 // until we've parsed the header.
ParseAttributes(nsDependentCString & aCookieHeader,nsCookieAttributes & aCookieAttributes)3758 bool nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
3759                                       nsCookieAttributes &aCookieAttributes) {
3760   static const char kPath[] = "path";
3761   static const char kDomain[] = "domain";
3762   static const char kExpires[] = "expires";
3763   static const char kMaxage[] = "max-age";
3764   static const char kSecure[] = "secure";
3765   static const char kHttpOnly[] = "httponly";
3766   static const char kSameSite[] = "samesite";
3767   static const char kSameSiteLax[] = "lax";
3768   static const char kSameSiteStrict[] = "strict";
3769 
3770   nsACString::const_char_iterator tempBegin, tempEnd;
3771   nsACString::const_char_iterator cookieStart, cookieEnd;
3772   aCookieHeader.BeginReading(cookieStart);
3773   aCookieHeader.EndReading(cookieEnd);
3774 
3775   aCookieAttributes.isSecure = false;
3776   aCookieAttributes.isHttpOnly = false;
3777   aCookieAttributes.sameSite = nsICookie2::SAMESITE_UNSET;
3778 
3779   nsDependentCSubstring tokenString(cookieStart, cookieStart);
3780   nsDependentCSubstring tokenValue(cookieStart, cookieStart);
3781   bool newCookie, equalsFound;
3782 
3783   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
3784   // if we find multiple cookies, return for processing
3785   // note: if there's no '=', we assume token is <VALUE>. this is required by
3786   //       some sites (see bug 169091).
3787   // XXX fix the parser to parse according to <VALUE> grammar for this case
3788   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
3789                             equalsFound);
3790   if (equalsFound) {
3791     aCookieAttributes.name = tokenString;
3792     aCookieAttributes.value = tokenValue;
3793   } else {
3794     aCookieAttributes.value = tokenString;
3795   }
3796 
3797   // extract remaining attributes
3798   while (cookieStart != cookieEnd && !newCookie) {
3799     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
3800                               equalsFound);
3801 
3802     if (!tokenValue.IsEmpty()) {
3803       tokenValue.BeginReading(tempBegin);
3804       tokenValue.EndReading(tempEnd);
3805     }
3806 
3807     // decide which attribute we have, and copy the string
3808     if (tokenString.LowerCaseEqualsLiteral(kPath))
3809       aCookieAttributes.path = tokenValue;
3810 
3811     else if (tokenString.LowerCaseEqualsLiteral(kDomain))
3812       aCookieAttributes.host = tokenValue;
3813 
3814     else if (tokenString.LowerCaseEqualsLiteral(kExpires))
3815       aCookieAttributes.expires = tokenValue;
3816 
3817     else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
3818       aCookieAttributes.maxage = tokenValue;
3819 
3820     // ignore any tokenValue for isSecure; just set the boolean
3821     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
3822       aCookieAttributes.isSecure = true;
3823 
3824     // ignore any tokenValue for isHttpOnly (see bug 178993);
3825     // just set the boolean
3826     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
3827       aCookieAttributes.isHttpOnly = true;
3828 
3829     else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
3830       if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
3831         aCookieAttributes.sameSite = nsICookie2::SAMESITE_LAX;
3832       } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
3833         aCookieAttributes.sameSite = nsICookie2::SAMESITE_STRICT;
3834       }
3835     }
3836   }
3837 
3838   // rebind aCookieHeader, in case we need to process another cookie
3839   aCookieHeader.Rebind(cookieStart, cookieEnd);
3840   return newCookie;
3841 }
3842 
3843 /******************************************************************************
3844  * nsCookieService impl:
3845  * private domain & permission compliance enforcement functions
3846  ******************************************************************************/
3847 
3848 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
3849 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
3850 // dot may be present. If aHostURI is an IP address, an alias such as
3851 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
3852 // be the exact host, and aRequireHostMatch will be true to indicate that
3853 // substring matches should not be performed.
GetBaseDomain(nsIEffectiveTLDService * aTLDService,nsIURI * aHostURI,nsCString & aBaseDomain,bool & aRequireHostMatch)3854 nsresult nsCookieService::GetBaseDomain(nsIEffectiveTLDService *aTLDService,
3855                                         nsIURI *aHostURI,
3856                                         nsCString &aBaseDomain,
3857                                         bool &aRequireHostMatch) {
3858   // get the base domain. this will fail if the host contains a leading dot,
3859   // more than one trailing dot, or is otherwise malformed.
3860   nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
3861   aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
3862                       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
3863   if (aRequireHostMatch) {
3864     // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
3865     // such as 'co.uk', or the empty string. use the host as a key in such
3866     // cases.
3867     rv = aHostURI->GetAsciiHost(aBaseDomain);
3868   }
3869   NS_ENSURE_SUCCESS(rv, rv);
3870 
3871   // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
3872   if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
3873     return NS_ERROR_INVALID_ARG;
3874 
3875   // block any URIs without a host that aren't file:// URIs.
3876   if (aBaseDomain.IsEmpty()) {
3877     bool isFileURI = false;
3878     aHostURI->SchemeIs("file", &isFileURI);
3879     if (!isFileURI) return NS_ERROR_INVALID_ARG;
3880   }
3881 
3882   return NS_OK;
3883 }
3884 
3885 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
3886 // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it
3887 // is assumed that aHost is already normalized, and it may contain a leading dot
3888 // (indicating that it represents a domain). A trailing dot may be present.
3889 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
3890 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
3891 // leading dot will be treated as an error.
GetBaseDomainFromHost(nsIEffectiveTLDService * aTLDService,const nsACString & aHost,nsCString & aBaseDomain)3892 nsresult nsCookieService::GetBaseDomainFromHost(
3893     nsIEffectiveTLDService *aTLDService, const nsACString &aHost,
3894     nsCString &aBaseDomain) {
3895   // aHost must not be the string '.'.
3896   if (aHost.Length() == 1 && aHost.Last() == '.') return NS_ERROR_INVALID_ARG;
3897 
3898   // aHost may contain a leading dot; if so, strip it now.
3899   bool domain = !aHost.IsEmpty() && aHost.First() == '.';
3900 
3901   // get the base domain. this will fail if the host contains a leading dot,
3902   // more than one trailing dot, or is otherwise malformed.
3903   nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0,
3904                                                    aBaseDomain);
3905   if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
3906       rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
3907     // aHost is either an IP address, an alias such as 'localhost', an eTLD
3908     // such as 'co.uk', or the empty string. use the host as a key in such
3909     // cases; however, we reject any such hosts with a leading dot, since it
3910     // doesn't make sense for them to be domain cookies.
3911     if (domain) return NS_ERROR_INVALID_ARG;
3912 
3913     aBaseDomain = aHost;
3914     return NS_OK;
3915   }
3916   return rv;
3917 }
3918 
3919 // Normalizes the given hostname, component by component. ASCII/ACE
3920 // components are lower-cased, and UTF-8 components are normalized per
3921 // RFC 3454 and converted to ACE.
NormalizeHost(nsCString & aHost)3922 nsresult nsCookieService::NormalizeHost(nsCString &aHost) {
3923   if (!IsASCII(aHost)) {
3924     nsAutoCString host;
3925     nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
3926     if (NS_FAILED(rv)) return rv;
3927 
3928     aHost = host;
3929   }
3930 
3931   ToLowerCase(aHost);
3932   return NS_OK;
3933 }
3934 
3935 // returns true if 'a' is equal to or a subdomain of 'b',
3936 // assuming no leading dots are present.
IsSubdomainOf(const nsCString & a,const nsCString & b)3937 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b) {
3938   if (a == b) return true;
3939   if (a.Length() > b.Length())
3940     return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
3941   return false;
3942 }
3943 
CheckPrefs(nsICookiePermission * aPermissionService,uint8_t aCookieBehavior,bool aThirdPartySession,bool aThirdPartyNonsecureSession,nsIURI * aHostURI,bool aIsForeign,const char * aCookieHeader,const int aNumOfCookies,const OriginAttributes & aOriginAttrs)3944 CookieStatus nsCookieService::CheckPrefs(
3945     nsICookiePermission *aPermissionService, uint8_t aCookieBehavior,
3946     bool aThirdPartySession, bool aThirdPartyNonsecureSession, nsIURI *aHostURI,
3947     bool aIsForeign, const char *aCookieHeader, const int aNumOfCookies,
3948     const OriginAttributes &aOriginAttrs) {
3949   nsresult rv;
3950 
3951   // don't let ftp sites get/set cookies (could be a security issue)
3952   bool ftp;
3953   if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
3954     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
3955                       aCookieHeader, "ftp sites cannot read cookies");
3956     return STATUS_REJECTED_WITH_ERROR;
3957   }
3958 
3959   nsCOMPtr<nsIPrincipal> principal =
3960       BasePrincipal::CreateCodebasePrincipal(aHostURI, aOriginAttrs);
3961 
3962   if (!principal) {
3963     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
3964                       aCookieHeader,
3965                       "non-codebase principals cannot get/set cookies");
3966     return STATUS_REJECTED_WITH_ERROR;
3967   }
3968 
3969   // check the permission list first; if we find an entry, it overrides
3970   // default prefs. see bug 184059.
3971   if (aPermissionService) {
3972     nsCookieAccess access;
3973     // Not passing an nsIChannel here is probably OK; our implementation
3974     // doesn't do anything with it anyway.
3975     rv = aPermissionService->CanAccess(principal, &access);
3976 
3977     // if we found an entry, use it
3978     if (NS_SUCCEEDED(rv)) {
3979       switch (access) {
3980         case nsICookiePermission::ACCESS_DENY:
3981           COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
3982                             aCookieHeader, "cookies are blocked for this site");
3983           return STATUS_REJECTED;
3984 
3985         case nsICookiePermission::ACCESS_ALLOW:
3986           return STATUS_ACCEPTED;
3987 
3988         case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
3989           if (aIsForeign) {
3990             COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
3991                               aCookieHeader,
3992                               "third party cookies are blocked "
3993                               "for this site");
3994             return STATUS_REJECTED;
3995           }
3996           return STATUS_ACCEPTED;
3997 
3998         case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
3999           if (!aIsForeign) return STATUS_ACCEPTED;
4000           if (aNumOfCookies == 0) {
4001             COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4002                               aCookieHeader,
4003                               "third party cookies are blocked "
4004                               "for this site");
4005             return STATUS_REJECTED;
4006           }
4007           return STATUS_ACCEPTED;
4008       }
4009     }
4010   }
4011 
4012   // check default prefs
4013   if (aCookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
4014     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4015                       aCookieHeader, "cookies are disabled");
4016     return STATUS_REJECTED;
4017   }
4018 
4019   // check if cookie is foreign
4020   if (aIsForeign) {
4021     if (aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
4022       COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4023                         aCookieHeader, "context is third party");
4024       return STATUS_REJECTED;
4025     }
4026 
4027     if (aCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
4028       if (aNumOfCookies == 0) {
4029         COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
4030                           aCookieHeader, "context is third party");
4031         return STATUS_REJECTED;
4032       }
4033     }
4034 
4035     MOZ_ASSERT(aCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT ||
4036                aCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN);
4037 
4038     if (aThirdPartySession) return STATUS_ACCEPT_SESSION;
4039 
4040     if (aThirdPartyNonsecureSession) {
4041       bool isHTTPS = false;
4042       aHostURI->SchemeIs("https", &isHTTPS);
4043       if (!isHTTPS) return STATUS_ACCEPT_SESSION;
4044     }
4045   }
4046 
4047   // if nothing has complained, accept cookie
4048   return STATUS_ACCEPTED;
4049 }
4050 
4051 // processes domain attribute, and returns true if host has permission to set
4052 // for this domain.
CheckDomain(nsCookieAttributes & aCookieAttributes,nsIURI * aHostURI,const nsCString & aBaseDomain,bool aRequireHostMatch)4053 bool nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
4054                                   nsIURI *aHostURI,
4055                                   const nsCString &aBaseDomain,
4056                                   bool aRequireHostMatch) {
4057   // Note: The logic in this function is mirrored in
4058   // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
4059   // If it changes, please update that function, or file a bug for someone
4060   // else to do so.
4061 
4062   // get host from aHostURI
4063   nsAutoCString hostFromURI;
4064   aHostURI->GetAsciiHost(hostFromURI);
4065 
4066   // if a domain is given, check the host has permission
4067   if (!aCookieAttributes.host.IsEmpty()) {
4068     // Tolerate leading '.' characters, but not if it's otherwise an empty host.
4069     if (aCookieAttributes.host.Length() > 1 &&
4070         aCookieAttributes.host.First() == '.') {
4071       aCookieAttributes.host.Cut(0, 1);
4072     }
4073 
4074     // switch to lowercase now, to avoid case-insensitive compares everywhere
4075     ToLowerCase(aCookieAttributes.host);
4076 
4077     // check whether the host is either an IP address, an alias such as
4078     // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
4079     // cases, require an exact string match for the domain, and leave the cookie
4080     // as a non-domain one. bug 105917 originally noted the requirement to deal
4081     // with IP addresses.
4082     if (aRequireHostMatch) return hostFromURI.Equals(aCookieAttributes.host);
4083 
4084     // ensure the proposed domain is derived from the base domain; and also
4085     // that the host domain is derived from the proposed domain (per RFC2109).
4086     if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
4087         IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
4088       // prepend a dot to indicate a domain cookie
4089       aCookieAttributes.host.InsertLiteral(".", 0);
4090       return true;
4091     }
4092 
4093     /*
4094      * note: RFC2109 section 4.3.2 requires that we check the following:
4095      * that the portion of host not in domain does not contain a dot.
4096      * this prevents hosts of the form x.y.co.nz from setting cookies in the
4097      * entire .co.nz domain. however, it's only a only a partial solution and
4098      * it breaks sites (IE doesn't enforce it), so we don't perform this check.
4099      */
4100     return false;
4101   }
4102 
4103   // no domain specified, use hostFromURI
4104   aCookieAttributes.host = hostFromURI;
4105   return true;
4106 }
4107 
GetPathFromURI(nsIURI * aHostURI)4108 nsCString nsCookieService::GetPathFromURI(nsIURI *aHostURI) {
4109   // strip down everything after the last slash to get the path,
4110   // ignoring slashes in the query string part.
4111   // if we can QI to nsIURL, that'll take care of the query string portion.
4112   // otherwise, it's not an nsIURL and can't have a query string, so just find
4113   // the last slash.
4114   nsAutoCString path;
4115   nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
4116   if (hostURL) {
4117     hostURL->GetDirectory(path);
4118   } else {
4119     aHostURI->GetPathQueryRef(path);
4120     int32_t slash = path.RFindChar('/');
4121     if (slash != kNotFound) {
4122       path.Truncate(slash + 1);
4123     }
4124   }
4125   return path;
4126 }
4127 
CheckPath(nsCookieAttributes & aCookieAttributes,nsIURI * aHostURI)4128 bool nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
4129                                 nsIURI *aHostURI) {
4130   // if a path is given, check the host has permission
4131   if (aCookieAttributes.path.IsEmpty() ||
4132       aCookieAttributes.path.First() != '/') {
4133     aCookieAttributes.path = GetPathFromURI(aHostURI);
4134 
4135 #if 0
4136   } else {
4137     /**
4138      * The following test is part of the RFC2109 spec.  Loosely speaking, it says that a site
4139      * cannot set a cookie for a path that it is not on.  See bug 155083.  However this patch
4140      * broke several sites -- nordea (bug 155768) and citibank (bug 156725).  So this test has
4141      * been disabled, unless we can evangelize these sites.
4142      */
4143     // get path from aHostURI
4144     nsAutoCString pathFromURI;
4145     if (NS_FAILED(aHostURI->GetPathQueryRef(pathFromURI)) ||
4146         !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
4147       return false;
4148     }
4149 #endif
4150   }
4151 
4152   if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
4153       aCookieAttributes.path.Contains('\t'))
4154     return false;
4155 
4156   return true;
4157 }
4158 
4159 // CheckPrefixes
4160 //
4161 // Reject cookies whose name starts with the magic prefixes from
4162 // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
4163 // if they do not meet the criteria required by the prefix.
4164 //
4165 // Must not be called until after CheckDomain() and CheckPath() have
4166 // regularized and validated the nsCookieAttributes values!
CheckPrefixes(nsCookieAttributes & aCookieAttributes,bool aSecureRequest)4167 bool nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes,
4168                                     bool aSecureRequest) {
4169   static const char kSecure[] = "__Secure-";
4170   static const char kHost[] = "__Host-";
4171   static const int kSecureLen = sizeof(kSecure) - 1;
4172   static const int kHostLen = sizeof(kHost) - 1;
4173 
4174   bool isSecure =
4175       strncmp(aCookieAttributes.name.get(), kSecure, kSecureLen) == 0;
4176   bool isHost = strncmp(aCookieAttributes.name.get(), kHost, kHostLen) == 0;
4177 
4178   if (!isSecure && !isHost) {
4179     // not one of the magic prefixes: carry on
4180     return true;
4181   }
4182 
4183   if (!aSecureRequest || !aCookieAttributes.isSecure) {
4184     // the magic prefixes may only be used from a secure request and
4185     // the secure attribute must be set on the cookie
4186     return false;
4187   }
4188 
4189   if (isHost) {
4190     // The host prefix requires that the path is "/" and that the cookie
4191     // had no domain attribute. CheckDomain() and CheckPath() MUST be run
4192     // first to make sure invalid attributes are rejected and to regularlize
4193     // them. In particular all explicit domain attributes result in a host
4194     // that starts with a dot, and if the host doesn't start with a dot it
4195     // correctly matches the true host.
4196     if (aCookieAttributes.host[0] == '.' ||
4197         !aCookieAttributes.path.EqualsLiteral("/")) {
4198       return false;
4199     }
4200   }
4201 
4202   return true;
4203 }
4204 
GetExpiry(nsCookieAttributes & aCookieAttributes,int64_t aServerTime,int64_t aCurrentTime)4205 bool nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
4206                                 int64_t aServerTime, int64_t aCurrentTime) {
4207   /* Determine when the cookie should expire. This is done by taking the
4208    * difference between the server time and the time the server wants the cookie
4209    * to expire, and adding that difference to the client time. This localizes
4210    * the client time regardless of whether or not the TZ environment variable
4211    * was set on the client.
4212    *
4213    * Note: We need to consider accounting for network lag here, per RFC.
4214    */
4215   // check for max-age attribute first; this overrides expires attribute
4216   if (!aCookieAttributes.maxage.IsEmpty()) {
4217     // obtain numeric value of maxageAttribute
4218     int64_t maxage;
4219     int32_t numInts =
4220         PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
4221 
4222     // default to session cookie if the conversion failed
4223     if (numInts != 1) {
4224       return true;
4225     }
4226 
4227     // if this addition overflows, expiryTime will be less than currentTime
4228     // and the cookie will be expired - that's okay.
4229     aCookieAttributes.expiryTime = aCurrentTime + maxage;
4230 
4231     // check for expires attribute
4232   } else if (!aCookieAttributes.expires.IsEmpty()) {
4233     PRTime expires;
4234 
4235     // parse expiry time
4236     if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) !=
4237         PR_SUCCESS) {
4238       return true;
4239     }
4240 
4241     // If set-cookie used absolute time to set expiration, and it can't use
4242     // client time to set expiration.
4243     // Because if current time be set in the future, but the cookie expire
4244     // time be set less than current time and more than server time.
4245     // The cookie item have to be used to the expired cookie.
4246     aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC);
4247 
4248     // default to session cookie if no attributes found
4249   } else {
4250     return true;
4251   }
4252 
4253   return false;
4254 }
4255 
4256 /******************************************************************************
4257  * nsCookieService impl:
4258  * private cookielist management functions
4259  ******************************************************************************/
4260 
RemoveAllFromMemory()4261 void nsCookieService::RemoveAllFromMemory() {
4262   // clearing the hashtable will call each nsCookieEntry's dtor,
4263   // which releases all their respective children.
4264   mDBState->hostTable.Clear();
4265   mDBState->cookieCount = 0;
4266   mDBState->cookieOldestTime = INT64_MAX;
4267 }
4268 
4269 // comparator class for lastaccessed times of cookies.
4270 class CompareCookiesByAge {
4271  public:
Equals(const nsListIter & a,const nsListIter & b) const4272   bool Equals(const nsListIter &a, const nsListIter &b) const {
4273     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
4274            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
4275   }
4276 
LessThan(const nsListIter & a,const nsListIter & b) const4277   bool LessThan(const nsListIter &a, const nsListIter &b) const {
4278     // compare by lastAccessed time, and tiebreak by creationTime.
4279     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
4280     if (result != 0) return result < 0;
4281 
4282     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
4283   }
4284 };
4285 
4286 // comparator class for sorting cookies by entry and index.
4287 class CompareCookiesByIndex {
4288  public:
Equals(const nsListIter & a,const nsListIter & b) const4289   bool Equals(const nsListIter &a, const nsListIter &b) const {
4290     NS_ASSERTION(a.entry != b.entry || a.index != b.index,
4291                  "cookie indexes should never be equal");
4292     return false;
4293   }
4294 
LessThan(const nsListIter & a,const nsListIter & b) const4295   bool LessThan(const nsListIter &a, const nsListIter &b) const {
4296     // compare by entryclass pointer, then by index.
4297     if (a.entry != b.entry) return a.entry < b.entry;
4298 
4299     return a.index < b.index;
4300   }
4301 };
4302 
4303 // purges expired and old cookies in a batch operation.
PurgeCookies(int64_t aCurrentTimeInUsec)4304 already_AddRefed<nsIArray> nsCookieService::PurgeCookies(
4305     int64_t aCurrentTimeInUsec) {
4306   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
4307 
4308   uint32_t initialCookieCount = mDBState->cookieCount;
4309   COOKIE_LOGSTRING(
4310       LogLevel::Debug,
4311       ("PurgeCookies(): beginning purge with %" PRIu32 " cookies and %" PRId64
4312        " oldest age",
4313        mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
4314 
4315   typedef nsTArray<nsListIter> PurgeList;
4316   PurgeList purgeList(kMaxNumberOfCookies);
4317 
4318   nsCOMPtr<nsIMutableArray> removedList =
4319       do_CreateInstance(NS_ARRAY_CONTRACTID);
4320 
4321   // Create a params array to batch the removals. This is OK here because
4322   // all the removals are in order, and there are no interleaved additions.
4323   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
4324   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
4325   if (mDBState->dbConn) {
4326     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
4327   }
4328 
4329   int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
4330   int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
4331   int64_t oldestTime = INT64_MAX;
4332 
4333   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4334     nsCookieEntry *entry = iter.Get();
4335 
4336     const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4337     auto length = cookies.Length();
4338     for (nsCookieEntry::IndexType i = 0; i < length;) {
4339       nsListIter iter(entry, i);
4340       nsCookie *cookie = cookies[i];
4341 
4342       // check if the cookie has expired
4343       if (cookie->Expiry() <= currentTime) {
4344         removedList->AppendElement(cookie);
4345         COOKIE_LOGEVICTED(cookie, "Cookie expired");
4346 
4347         // remove from list; do not increment our iterator, but stop if we're
4348         // done already.
4349         gCookieService->RemoveCookieFromList(iter, paramsArray);
4350         if (i == --length) {
4351           break;
4352         }
4353       } else {
4354         // check if the cookie is over the age limit
4355         if (cookie->LastAccessed() <= purgeTime) {
4356           purgeList.AppendElement(iter);
4357 
4358         } else if (cookie->LastAccessed() < oldestTime) {
4359           // reset our indicator
4360           oldestTime = cookie->LastAccessed();
4361         }
4362 
4363         ++i;
4364       }
4365       MOZ_ASSERT(length == cookies.Length());
4366     }
4367   }
4368 
4369   uint32_t postExpiryCookieCount = mDBState->cookieCount;
4370 
4371   // now we have a list of iterators for cookies over the age limit.
4372   // sort them by age, and then we'll see how many to remove...
4373   purgeList.Sort(CompareCookiesByAge());
4374 
4375   // only remove old cookies until we reach the max cookie limit, no more.
4376   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies
4377                         ? mDBState->cookieCount - mMaxNumberOfCookies
4378                         : 0;
4379   if (purgeList.Length() > excess) {
4380     // We're not purging everything in the list, so update our indicator.
4381     oldestTime = purgeList[excess].Cookie()->LastAccessed();
4382 
4383     purgeList.SetLength(excess);
4384   }
4385 
4386   // sort the list again, this time grouping cookies with a common entryclass
4387   // together, and with ascending index. this allows us to iterate backwards
4388   // over the list removing cookies, without having to adjust indexes as we go.
4389   purgeList.Sort(CompareCookiesByIndex());
4390   for (PurgeList::index_type i = purgeList.Length(); i--;) {
4391     nsCookie *cookie = purgeList[i].Cookie();
4392     removedList->AppendElement(cookie);
4393     COOKIE_LOGEVICTED(cookie, "Cookie too old");
4394 
4395     RemoveCookieFromList(purgeList[i], paramsArray);
4396   }
4397 
4398   // Update the database if we have entries to purge.
4399   if (paramsArray) {
4400     uint32_t length;
4401     paramsArray->GetLength(&length);
4402     if (length) {
4403       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
4404       NS_ASSERT_SUCCESS(rv);
4405       nsCOMPtr<mozIStoragePendingStatement> handle;
4406       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
4407       NS_ASSERT_SUCCESS(rv);
4408     }
4409   }
4410 
4411   // reset the oldest time indicator
4412   mDBState->cookieOldestTime = oldestTime;
4413 
4414   COOKIE_LOGSTRING(
4415       LogLevel::Debug,
4416       ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32 " purged; %" PRIu32
4417        " remain; %" PRId64 " oldest age",
4418        initialCookieCount - postExpiryCookieCount,
4419        postExpiryCookieCount - mDBState->cookieCount, mDBState->cookieCount,
4420        aCurrentTimeInUsec - mDBState->cookieOldestTime));
4421 
4422   return removedList.forget();
4423 }
4424 
4425 // find whether a given cookie has been previously set. this is provided by the
4426 // nsICookieManager interface.
4427 NS_IMETHODIMP
CookieExists(nsICookie2 * aCookie,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,bool * aFoundCookie)4428 nsCookieService::CookieExists(nsICookie2 *aCookie,
4429                               JS::HandleValue aOriginAttributes, JSContext *aCx,
4430                               uint8_t aArgc, bool *aFoundCookie) {
4431   NS_ENSURE_ARG_POINTER(aCookie);
4432   NS_ENSURE_ARG_POINTER(aCx);
4433   NS_ENSURE_ARG_POINTER(aFoundCookie);
4434   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
4435 
4436   OriginAttributes attrs;
4437   nsresult rv =
4438       InitializeOriginAttributes(&attrs, aOriginAttributes, aCx, aArgc,
4439                                  u"nsICookieManager.cookieExists()", u"2");
4440   NS_ENSURE_SUCCESS(rv, rv);
4441 
4442   return CookieExistsNative(aCookie, &attrs, aFoundCookie);
4443 }
4444 
NS_IMETHODIMP_(nsresult)4445 NS_IMETHODIMP_(nsresult)
4446 nsCookieService::CookieExistsNative(nsICookie2 *aCookie,
4447                                     OriginAttributes *aOriginAttributes,
4448                                     bool *aFoundCookie) {
4449   NS_ENSURE_ARG_POINTER(aCookie);
4450   NS_ENSURE_ARG_POINTER(aOriginAttributes);
4451   NS_ENSURE_ARG_POINTER(aFoundCookie);
4452 
4453   if (!mDBState) {
4454     NS_WARNING("No DBState! Profile already closed?");
4455     return NS_ERROR_NOT_AVAILABLE;
4456   }
4457 
4458   EnsureReadComplete(true);
4459 
4460   AutoRestore<DBState *> savePrevDBState(mDBState);
4461   mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState
4462                                                          : mDefaultDBState;
4463 
4464   nsAutoCString host, name, path;
4465   nsresult rv = aCookie->GetHost(host);
4466   NS_ENSURE_SUCCESS(rv, rv);
4467   rv = aCookie->GetName(name);
4468   NS_ENSURE_SUCCESS(rv, rv);
4469   rv = aCookie->GetPath(path);
4470   NS_ENSURE_SUCCESS(rv, rv);
4471 
4472   nsAutoCString baseDomain;
4473   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
4474   NS_ENSURE_SUCCESS(rv, rv);
4475 
4476   nsListIter iter;
4477   *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes), host,
4478                              name, path, iter);
4479   return NS_OK;
4480 }
4481 
4482 // For a given base domain, find either an expired cookie or the oldest cookie
4483 // by lastAccessed time.
FindStaleCookie(nsCookieEntry * aEntry,int64_t aCurrentTime,nsIURI * aSource,const mozilla::Maybe<bool> & aIsSecure,nsListIter & aIter)4484 int64_t nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
4485                                          int64_t aCurrentTime, nsIURI *aSource,
4486                                          const mozilla::Maybe<bool> &aIsSecure,
4487                                          nsListIter &aIter) {
4488   aIter.entry = nullptr;
4489   bool requireHostMatch = true;
4490   nsAutoCString baseDomain, sourceHost, sourcePath;
4491   if (aSource) {
4492     GetBaseDomain(mTLDService, aSource, baseDomain, requireHostMatch);
4493     aSource->GetAsciiHost(sourceHost);
4494     sourcePath = GetPathFromURI(aSource);
4495   }
4496 
4497   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
4498 
4499   int64_t oldestNonMatchingCookieTime = 0;
4500   nsListIter oldestNonMatchingCookie;
4501   oldestNonMatchingCookie.entry = nullptr;
4502 
4503   int64_t oldestCookieTime = 0;
4504   nsListIter oldestCookie;
4505   oldestCookie.entry = nullptr;
4506 
4507   int64_t actualOldestCookieTime =
4508       cookies.Length() ? cookies[0]->LastAccessed() : 0;
4509   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4510     nsCookie *cookie = cookies[i];
4511 
4512     // If we found an expired cookie, we're done.
4513     if (cookie->Expiry() <= aCurrentTime) {
4514       aIter.entry = aEntry;
4515       aIter.index = i;
4516       return -1;
4517     }
4518 
4519     int64_t lastAccessed = cookie->LastAccessed();
4520     // Record the age of the oldest cookie that is stored for this host.
4521     // oldestCookieTime is the age of the oldest cookie with a matching
4522     // secure flag, which may be more recent than an older cookie with
4523     // a non-matching secure flag.
4524     if (actualOldestCookieTime > lastAccessed) {
4525       actualOldestCookieTime = lastAccessed;
4526     }
4527     if (aIsSecure.isSome() && !aIsSecure.value()) {
4528       // We want to look for the oldest non-secure cookie first time through,
4529       // then find the oldest secure cookie the second time we are called.
4530       if (cookie->IsSecure()) {
4531         continue;
4532       }
4533     }
4534 
4535     // This cookie is a candidate for eviction if we have no information about
4536     // the source request, or if it is not a path or domain match against the
4537     // source request.
4538     bool isPrimaryEvictionCandidate = true;
4539     if (aSource) {
4540       isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) ||
4541                                    !DomainMatches(cookie, sourceHost);
4542     }
4543 
4544     if (isPrimaryEvictionCandidate &&
4545         (!oldestNonMatchingCookie.entry ||
4546          oldestNonMatchingCookieTime > lastAccessed)) {
4547       oldestNonMatchingCookieTime = lastAccessed;
4548       oldestNonMatchingCookie.entry = aEntry;
4549       oldestNonMatchingCookie.index = i;
4550     }
4551 
4552     // Check if we've found the oldest cookie so far.
4553     if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
4554       oldestCookieTime = lastAccessed;
4555       oldestCookie.entry = aEntry;
4556       oldestCookie.index = i;
4557     }
4558   }
4559 
4560   // Prefer to evict the oldest cookie with a non-matching path/domain,
4561   // followed by the oldest matching cookie.
4562   if (oldestNonMatchingCookie.entry) {
4563     aIter = oldestNonMatchingCookie;
4564   } else {
4565     aIter = oldestCookie;
4566   }
4567 
4568   return actualOldestCookieTime;
4569 }
4570 
TelemetryForEvictingStaleCookie(nsCookie * aEvicted,int64_t oldestCookieTime)4571 void nsCookieService::TelemetryForEvictingStaleCookie(
4572     nsCookie *aEvicted, int64_t oldestCookieTime) {
4573   // We need to record the evicting cookie to telemetry.
4574   if (!aEvicted->IsSecure()) {
4575     if (aEvicted->LastAccessed() > oldestCookieTime) {
4576       Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4577                             EVICTED_NEWER_INSECURE);
4578     } else {
4579       Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4580                             EVICTED_OLDEST_COOKIE);
4581     }
4582   } else {
4583     Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
4584                           EVICTED_PREFERRED_COOKIE);
4585   }
4586 }
4587 
4588 // count the number of cookies stored by a particular host. this is provided by
4589 // the nsICookieManager interface.
4590 NS_IMETHODIMP
CountCookiesFromHost(const nsACString & aHost,uint32_t * aCountFromHost)4591 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
4592                                       uint32_t *aCountFromHost) {
4593   if (!mDBState) {
4594     NS_WARNING("No DBState! Profile already closed?");
4595     return NS_ERROR_NOT_AVAILABLE;
4596   }
4597 
4598   EnsureReadComplete(true);
4599 
4600   // first, normalize the hostname, and fail if it contains illegal characters.
4601   nsAutoCString host(aHost);
4602   nsresult rv = NormalizeHost(host);
4603   NS_ENSURE_SUCCESS(rv, rv);
4604 
4605   nsAutoCString baseDomain;
4606   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
4607   NS_ENSURE_SUCCESS(rv, rv);
4608 
4609   nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
4610 
4611   // Return a count of all cookies, including expired.
4612   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
4613   *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
4614   return NS_OK;
4615 }
4616 
4617 // get an enumerator of cookies stored by a particular host. this is provided by
4618 // the nsICookieManager interface.
4619 NS_IMETHODIMP
GetCookiesFromHost(const nsACString & aHost,JS::HandleValue aOriginAttributes,JSContext * aCx,uint8_t aArgc,nsISimpleEnumerator ** aEnumerator)4620 nsCookieService::GetCookiesFromHost(const nsACString &aHost,
4621                                     JS::HandleValue aOriginAttributes,
4622                                     JSContext *aCx, uint8_t aArgc,
4623                                     nsISimpleEnumerator **aEnumerator) {
4624   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
4625 
4626   if (!mDBState) {
4627     NS_WARNING("No DBState! Profile already closed?");
4628     return NS_ERROR_NOT_AVAILABLE;
4629   }
4630 
4631   EnsureReadComplete(true);
4632 
4633   // first, normalize the hostname, and fail if it contains illegal characters.
4634   nsAutoCString host(aHost);
4635   nsresult rv = NormalizeHost(host);
4636   NS_ENSURE_SUCCESS(rv, rv);
4637 
4638   nsAutoCString baseDomain;
4639   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
4640   NS_ENSURE_SUCCESS(rv, rv);
4641 
4642   OriginAttributes attrs;
4643   rv = InitializeOriginAttributes(&attrs, aOriginAttributes, aCx, aArgc,
4644                                   u"nsICookieManager.getCookiesFromHost()",
4645                                   u"2");
4646   NS_ENSURE_SUCCESS(rv, rv);
4647 
4648   AutoRestore<DBState *> savePrevDBState(mDBState);
4649   mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState;
4650 
4651   nsCookieKey key = nsCookieKey(baseDomain, attrs);
4652 
4653   nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
4654   if (!entry) return NS_NewEmptyEnumerator(aEnumerator);
4655 
4656   nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
4657   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4658   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4659     cookieList.AppendObject(cookies[i]);
4660   }
4661 
4662   return NS_NewArrayEnumerator(aEnumerator, cookieList);
4663 }
4664 
4665 NS_IMETHODIMP
GetCookiesWithOriginAttributes(const nsAString & aPattern,const nsACString & aHost,nsISimpleEnumerator ** aEnumerator)4666 nsCookieService::GetCookiesWithOriginAttributes(
4667     const nsAString &aPattern, const nsACString &aHost,
4668     nsISimpleEnumerator **aEnumerator) {
4669   mozilla::OriginAttributesPattern pattern;
4670   if (!pattern.Init(aPattern)) {
4671     return NS_ERROR_INVALID_ARG;
4672   }
4673 
4674   nsAutoCString host(aHost);
4675   nsresult rv = NormalizeHost(host);
4676   NS_ENSURE_SUCCESS(rv, rv);
4677 
4678   nsAutoCString baseDomain;
4679   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
4680   NS_ENSURE_SUCCESS(rv, rv);
4681 
4682   return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator);
4683 }
4684 
GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern & aPattern,const nsCString & aBaseDomain,nsISimpleEnumerator ** aEnumerator)4685 nsresult nsCookieService::GetCookiesWithOriginAttributes(
4686     const mozilla::OriginAttributesPattern &aPattern,
4687     const nsCString &aBaseDomain, nsISimpleEnumerator **aEnumerator) {
4688   if (!mDBState) {
4689     NS_WARNING("No DBState! Profile already closed?");
4690     return NS_ERROR_NOT_AVAILABLE;
4691   }
4692   EnsureReadComplete(true);
4693 
4694   AutoRestore<DBState *> savePrevDBState(mDBState);
4695   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
4696               aPattern.mPrivateBrowsingId.Value() > 0)
4697                  ? mPrivateDBState
4698                  : mDefaultDBState;
4699 
4700   nsCOMArray<nsICookie> cookies;
4701   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4702     nsCookieEntry *entry = iter.Get();
4703 
4704     if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
4705       continue;
4706     }
4707 
4708     if (!aPattern.Matches(entry->mOriginAttributes)) {
4709       continue;
4710     }
4711 
4712     const nsCookieEntry::ArrayType &entryCookies = entry->GetCookies();
4713 
4714     for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
4715       cookies.AppendObject(entryCookies[i]);
4716     }
4717   }
4718 
4719   return NS_NewArrayEnumerator(aEnumerator, cookies);
4720 }
4721 
4722 NS_IMETHODIMP
RemoveCookiesWithOriginAttributes(const nsAString & aPattern,const nsACString & aHost)4723 nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString &aPattern,
4724                                                    const nsACString &aHost) {
4725   MOZ_ASSERT(XRE_IsParentProcess());
4726 
4727   mozilla::OriginAttributesPattern pattern;
4728   if (!pattern.Init(aPattern)) {
4729     return NS_ERROR_INVALID_ARG;
4730   }
4731 
4732   nsAutoCString host(aHost);
4733   nsresult rv = NormalizeHost(host);
4734   NS_ENSURE_SUCCESS(rv, rv);
4735 
4736   nsAutoCString baseDomain;
4737   rv = GetBaseDomainFromHost(mTLDService, host, baseDomain);
4738   NS_ENSURE_SUCCESS(rv, rv);
4739 
4740   return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
4741 }
4742 
RemoveCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern & aPattern,const nsCString & aBaseDomain)4743 nsresult nsCookieService::RemoveCookiesWithOriginAttributes(
4744     const mozilla::OriginAttributesPattern &aPattern,
4745     const nsCString &aBaseDomain) {
4746   if (!mDBState) {
4747     NS_WARNING("No DBState! Profile already close?");
4748     return NS_ERROR_NOT_AVAILABLE;
4749   }
4750 
4751   EnsureReadComplete(true);
4752 
4753   AutoRestore<DBState *> savePrevDBState(mDBState);
4754   mDBState = (aPattern.mPrivateBrowsingId.WasPassed() &&
4755               aPattern.mPrivateBrowsingId.Value() > 0)
4756                  ? mPrivateDBState
4757                  : mDefaultDBState;
4758 
4759   mozStorageTransaction transaction(mDBState->dbConn, false);
4760   // Iterate the hash table of nsCookieEntry.
4761   for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
4762     nsCookieEntry *entry = iter.Get();
4763 
4764     if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
4765       continue;
4766     }
4767 
4768     if (!aPattern.Matches(entry->mOriginAttributes)) {
4769       continue;
4770     }
4771 
4772     // Pattern matches. Delete all cookies within this nsCookieEntry.
4773     uint32_t cookiesCount = entry->GetCookies().Length();
4774 
4775     for (nsCookieEntry::IndexType i = 0; i < cookiesCount; ++i) {
4776       // Remove the first cookie from the list.
4777       nsListIter iter(entry, 0);
4778       RefPtr<nsCookie> cookie = iter.Cookie();
4779 
4780       // Remove the cookie.
4781       RemoveCookieFromList(iter);
4782 
4783       if (cookie) {
4784         NotifyChanged(cookie, u"deleted");
4785       }
4786     }
4787   }
4788   DebugOnly<nsresult> rv = transaction.Commit();
4789   MOZ_ASSERT(NS_SUCCEEDED(rv));
4790 
4791   return NS_OK;
4792 }
4793 
4794 // find an secure cookie specified by host and name
FindSecureCookie(const nsCookieKey & aKey,nsCookie * aCookie)4795 bool nsCookieService::FindSecureCookie(const nsCookieKey &aKey,
4796                                        nsCookie *aCookie) {
4797   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
4798   if (!entry) return false;
4799 
4800   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4801   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4802     nsCookie *cookie = cookies[i];
4803     // isn't a match if insecure or a different name
4804     if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name()))
4805       continue;
4806 
4807     // The host must "domain-match" an existing cookie or vice-versa
4808     if (DomainMatches(cookie, aCookie->Host()) ||
4809         DomainMatches(aCookie, cookie->Host())) {
4810       // If the path of new cookie and the path of existing cookie
4811       // aren't "/", then this situation needs to compare paths to
4812       // ensure only that a newly-created non-secure cookie does not
4813       // overlay an existing secure cookie.
4814       if (PathMatches(cookie, aCookie->Path())) {
4815         return true;
4816       }
4817     }
4818   }
4819 
4820   return false;
4821 }
4822 
4823 // find an exact cookie specified by host, name, and path that hasn't expired.
FindCookie(const nsCookieKey & aKey,const nsCString & aHost,const nsCString & aName,const nsCString & aPath,nsListIter & aIter)4824 bool nsCookieService::FindCookie(const nsCookieKey &aKey,
4825                                  const nsCString &aHost, const nsCString &aName,
4826                                  const nsCString &aPath, nsListIter &aIter) {
4827   // Should |EnsureReadComplete| before.
4828   MOZ_ASSERT(mInitializedDBStates);
4829   MOZ_ASSERT(mInitializedDBConn);
4830 
4831   nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
4832   if (!entry) return false;
4833 
4834   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
4835   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
4836     nsCookie *cookie = cookies[i];
4837 
4838     if (aHost.Equals(cookie->Host()) && aPath.Equals(cookie->Path()) &&
4839         aName.Equals(cookie->Name())) {
4840       aIter = nsListIter(entry, i);
4841       return true;
4842     }
4843   }
4844 
4845   return false;
4846 }
4847 
4848 // remove a cookie from the hashtable, and update the iterator state.
RemoveCookieFromList(const nsListIter & aIter,mozIStorageBindingParamsArray * aParamsArray)4849 void nsCookieService::RemoveCookieFromList(
4850     const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray) {
4851   // if it's a non-session cookie, remove it from the db
4852   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
4853     // Use the asynchronous binding methods to ensure that we do not acquire
4854     // the database lock.
4855     mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
4856     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
4857     if (!paramsArray) {
4858       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
4859     }
4860 
4861     nsCOMPtr<mozIStorageBindingParams> params;
4862     paramsArray->NewBindingParams(getter_AddRefs(params));
4863 
4864     DebugOnly<nsresult> rv = params->BindUTF8StringByName(
4865         NS_LITERAL_CSTRING("name"), aIter.Cookie()->Name());
4866     NS_ASSERT_SUCCESS(rv);
4867 
4868     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
4869                                       aIter.Cookie()->Host());
4870     NS_ASSERT_SUCCESS(rv);
4871 
4872     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
4873                                       aIter.Cookie()->Path());
4874     NS_ASSERT_SUCCESS(rv);
4875 
4876     nsAutoCString suffix;
4877     aIter.Cookie()->OriginAttributesRef().CreateSuffix(suffix);
4878     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
4879                                       suffix);
4880     NS_ASSERT_SUCCESS(rv);
4881 
4882     rv = paramsArray->AddParams(params);
4883     NS_ASSERT_SUCCESS(rv);
4884 
4885     // If we weren't given a params array, we'll need to remove it ourselves.
4886     if (!aParamsArray) {
4887       rv = stmt->BindParameters(paramsArray);
4888       NS_ASSERT_SUCCESS(rv);
4889       nsCOMPtr<mozIStoragePendingStatement> handle;
4890       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
4891       NS_ASSERT_SUCCESS(rv);
4892     }
4893   }
4894 
4895   if (aIter.entry->GetCookies().Length() == 1) {
4896     // we're removing the last element in the array - so just remove the entry
4897     // from the hash. note that the entryclass' dtor will take care of
4898     // releasing this last element for us!
4899     mDBState->hostTable.RawRemoveEntry(aIter.entry);
4900 
4901   } else {
4902     // just remove the element from the list
4903     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
4904   }
4905 
4906   --mDBState->cookieCount;
4907 }
4908 
bindCookieParameters(mozIStorageBindingParamsArray * aParamsArray,const nsCookieKey & aKey,const nsCookie * aCookie)4909 void bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
4910                           const nsCookieKey &aKey, const nsCookie *aCookie) {
4911   NS_ASSERTION(aParamsArray,
4912                "Null params array passed to bindCookieParameters!");
4913   NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
4914 
4915   // Use the asynchronous binding methods to ensure that we do not acquire the
4916   // database lock.
4917   nsCOMPtr<mozIStorageBindingParams> params;
4918   DebugOnly<nsresult> rv =
4919       aParamsArray->NewBindingParams(getter_AddRefs(params));
4920   NS_ASSERT_SUCCESS(rv);
4921 
4922   // Bind our values to params
4923   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
4924                                     aKey.mBaseDomain);
4925   NS_ASSERT_SUCCESS(rv);
4926 
4927   nsAutoCString suffix;
4928   aKey.mOriginAttributes.CreateSuffix(suffix);
4929   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
4930                                     suffix);
4931   NS_ASSERT_SUCCESS(rv);
4932 
4933   rv =
4934       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), aCookie->Name());
4935   NS_ASSERT_SUCCESS(rv);
4936 
4937   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
4938                                     aCookie->Value());
4939   NS_ASSERT_SUCCESS(rv);
4940 
4941   rv =
4942       params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), aCookie->Host());
4943   NS_ASSERT_SUCCESS(rv);
4944 
4945   rv =
4946       params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), aCookie->Path());
4947   NS_ASSERT_SUCCESS(rv);
4948 
4949   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"), aCookie->Expiry());
4950   NS_ASSERT_SUCCESS(rv);
4951 
4952   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
4953                                aCookie->LastAccessed());
4954   NS_ASSERT_SUCCESS(rv);
4955 
4956   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
4957                                aCookie->CreationTime());
4958   NS_ASSERT_SUCCESS(rv);
4959 
4960   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
4961                                aCookie->IsSecure());
4962   NS_ASSERT_SUCCESS(rv);
4963 
4964   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
4965                                aCookie->IsHttpOnly());
4966   NS_ASSERT_SUCCESS(rv);
4967 
4968   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"),
4969                                aCookie->SameSite());
4970   NS_ASSERT_SUCCESS(rv);
4971 
4972   // Bind the params to the array.
4973   rv = aParamsArray->AddParams(params);
4974   NS_ASSERT_SUCCESS(rv);
4975 }
4976 
UpdateCookieOldestTime(DBState * aDBState,nsCookie * aCookie)4977 void nsCookieService::UpdateCookieOldestTime(DBState *aDBState,
4978                                              nsCookie *aCookie) {
4979   if (aCookie->LastAccessed() < aDBState->cookieOldestTime) {
4980     aDBState->cookieOldestTime = aCookie->LastAccessed();
4981   }
4982 }
4983 
AddCookieToList(const nsCookieKey & aKey,nsCookie * aCookie,DBState * aDBState,mozIStorageBindingParamsArray * aParamsArray,bool aWriteToDB)4984 void nsCookieService::AddCookieToList(
4985     const nsCookieKey &aKey, nsCookie *aCookie, DBState *aDBState,
4986     mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB) {
4987   NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
4988                "Not writing to the DB but have a params array?");
4989   NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
4990                "Do not have a DB connection but have a params array?");
4991 
4992   if (!aCookie) {
4993     NS_WARNING("Attempting to AddCookieToList with null cookie");
4994     return;
4995   }
4996 
4997   nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
4998   NS_ASSERTION(entry, "can't insert element into a null entry!");
4999 
5000   entry->GetCookies().AppendElement(aCookie);
5001   ++aDBState->cookieCount;
5002 
5003   // keep track of the oldest cookie, for when it comes time to purge
5004   UpdateCookieOldestTime(aDBState, aCookie);
5005 
5006   // if it's a non-session cookie and hasn't just been read from the db, write
5007   // it out.
5008   if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
5009     mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
5010     nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
5011     if (!paramsArray) {
5012       stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
5013     }
5014     bindCookieParameters(paramsArray, aKey, aCookie);
5015 
5016     // If we were supplied an array to store parameters, we shouldn't call
5017     // executeAsync - someone up the stack will do this for us.
5018     if (!aParamsArray) {
5019       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
5020       NS_ASSERT_SUCCESS(rv);
5021       nsCOMPtr<mozIStoragePendingStatement> handle;
5022       rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
5023       NS_ASSERT_SUCCESS(rv);
5024     }
5025   }
5026 }
5027 
UpdateCookieInList(nsCookie * aCookie,int64_t aLastAccessed,mozIStorageBindingParamsArray * aParamsArray)5028 void nsCookieService::UpdateCookieInList(
5029     nsCookie *aCookie, int64_t aLastAccessed,
5030     mozIStorageBindingParamsArray *aParamsArray) {
5031   NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
5032 
5033   // udpate the lastAccessed timestamp
5034   aCookie->SetLastAccessed(aLastAccessed);
5035 
5036   // if it's a non-session cookie, update it in the db too
5037   if (!aCookie->IsSession() && aParamsArray) {
5038     // Create our params holder.
5039     nsCOMPtr<mozIStorageBindingParams> params;
5040     aParamsArray->NewBindingParams(getter_AddRefs(params));
5041 
5042     // Bind our parameters.
5043     DebugOnly<nsresult> rv = params->BindInt64ByName(
5044         NS_LITERAL_CSTRING("lastAccessed"), aLastAccessed);
5045     NS_ASSERT_SUCCESS(rv);
5046 
5047     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
5048                                       aCookie->Name());
5049     NS_ASSERT_SUCCESS(rv);
5050 
5051     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
5052                                       aCookie->Host());
5053     NS_ASSERT_SUCCESS(rv);
5054 
5055     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
5056                                       aCookie->Path());
5057     NS_ASSERT_SUCCESS(rv);
5058 
5059     nsAutoCString suffix;
5060     aCookie->OriginAttributesRef().CreateSuffix(suffix);
5061     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
5062                                       suffix);
5063     NS_ASSERT_SUCCESS(rv);
5064 
5065     // Add our bound parameters to the array.
5066     rv = aParamsArray->AddParams(params);
5067     NS_ASSERT_SUCCESS(rv);
5068   }
5069 }
5070 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const5071 size_t nsCookieService::SizeOfIncludingThis(
5072     mozilla::MallocSizeOf aMallocSizeOf) const {
5073   size_t n = aMallocSizeOf(this);
5074 
5075   if (mDefaultDBState) {
5076     n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
5077   }
5078   if (mPrivateDBState) {
5079     n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
5080   }
5081 
5082   return n;
5083 }
5084 
MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)5085 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
5086 
5087 NS_IMETHODIMP
5088 nsCookieService::CollectReports(nsIHandleReportCallback *aHandleReport,
5089                                 nsISupports *aData, bool aAnonymize) {
5090   MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
5091                      SizeOfIncludingThis(CookieServiceMallocSizeOf),
5092                      "Memory used by the cookie service.");
5093 
5094   return NS_OK;
5095 }
5096