1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "Cookie.h"
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "CookiePersistentStorage.h"
10 
11 #include "mozilla/FileUtils.h"
12 #include "mozilla/Telemetry.h"
13 #include "mozIStorageAsyncStatement.h"
14 #include "mozIStorageError.h"
15 #include "mozIStorageFunction.h"
16 #include "mozIStorageService.h"
17 #include "mozStorageHelper.h"
18 #include "nsAppDirectoryServiceDefs.h"
19 #include "nsICookieService.h"
20 #include "nsIEffectiveTLDService.h"
21 #include "nsILineInputStream.h"
22 #include "nsNetUtil.h"
23 #include "nsVariant.h"
24 #include "prprf.h"
25 
26 // XXX_hack. See bug 178993.
27 // This is a hack to hide HttpOnly cookies from older browsers
28 #define HTTP_ONLY_PREFIX "#HttpOnly_"
29 
30 constexpr auto COOKIES_SCHEMA_VERSION = 11;
31 
32 // parameter indexes; see |Read|
33 constexpr auto IDX_NAME = 0;
34 constexpr auto IDX_VALUE = 1;
35 constexpr auto IDX_HOST = 2;
36 constexpr auto IDX_PATH = 3;
37 constexpr auto IDX_EXPIRY = 4;
38 constexpr auto IDX_LAST_ACCESSED = 5;
39 constexpr auto IDX_CREATION_TIME = 6;
40 constexpr auto IDX_SECURE = 7;
41 constexpr auto IDX_HTTPONLY = 8;
42 constexpr auto IDX_ORIGIN_ATTRIBUTES = 9;
43 constexpr auto IDX_SAME_SITE = 10;
44 constexpr auto IDX_RAW_SAME_SITE = 11;
45 
46 #define COOKIES_FILE "cookies.sqlite"
47 
48 namespace mozilla {
49 namespace net {
50 
51 namespace {
52 
BindCookieParameters(mozIStorageBindingParamsArray * aParamsArray,const CookieKey & aKey,const Cookie * aCookie)53 void BindCookieParameters(mozIStorageBindingParamsArray* aParamsArray,
54                           const CookieKey& aKey, const Cookie* aCookie) {
55   NS_ASSERTION(aParamsArray,
56                "Null params array passed to BindCookieParameters!");
57   NS_ASSERTION(aCookie, "Null cookie passed to BindCookieParameters!");
58 
59   // Use the asynchronous binding methods to ensure that we do not acquire the
60   // database lock.
61   nsCOMPtr<mozIStorageBindingParams> params;
62   DebugOnly<nsresult> rv =
63       aParamsArray->NewBindingParams(getter_AddRefs(params));
64   MOZ_ASSERT(NS_SUCCEEDED(rv));
65 
66   nsAutoCString suffix;
67   aKey.mOriginAttributes.CreateSuffix(suffix);
68   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
69                                     suffix);
70   MOZ_ASSERT(NS_SUCCEEDED(rv));
71 
72   rv =
73       params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), aCookie->Name());
74   MOZ_ASSERT(NS_SUCCEEDED(rv));
75 
76   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
77                                     aCookie->Value());
78   MOZ_ASSERT(NS_SUCCEEDED(rv));
79 
80   rv =
81       params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), aCookie->Host());
82   MOZ_ASSERT(NS_SUCCEEDED(rv));
83 
84   rv =
85       params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), aCookie->Path());
86   MOZ_ASSERT(NS_SUCCEEDED(rv));
87 
88   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"), aCookie->Expiry());
89   MOZ_ASSERT(NS_SUCCEEDED(rv));
90 
91   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
92                                aCookie->LastAccessed());
93   MOZ_ASSERT(NS_SUCCEEDED(rv));
94 
95   rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
96                                aCookie->CreationTime());
97   MOZ_ASSERT(NS_SUCCEEDED(rv));
98 
99   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
100                                aCookie->IsSecure());
101   MOZ_ASSERT(NS_SUCCEEDED(rv));
102 
103   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
104                                aCookie->IsHttpOnly());
105   MOZ_ASSERT(NS_SUCCEEDED(rv));
106 
107   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"),
108                                aCookie->SameSite());
109   MOZ_ASSERT(NS_SUCCEEDED(rv));
110 
111   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("rawSameSite"),
112                                aCookie->RawSameSite());
113   MOZ_ASSERT(NS_SUCCEEDED(rv));
114 
115   // Bind the params to the array.
116   rv = aParamsArray->AddParams(params);
117   MOZ_ASSERT(NS_SUCCEEDED(rv));
118 }
119 
120 class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction {
121   ~ConvertAppIdToOriginAttrsSQLFunction() = default;
122 
123   NS_DECL_ISUPPORTS
124   NS_DECL_MOZISTORAGEFUNCTION
125 };
126 
127 NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
128 
129 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)130 ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
131     mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
132   nsresult rv;
133   int32_t inIsolatedMozBrowser;
134 
135   rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
136   NS_ENSURE_SUCCESS(rv, rv);
137 
138   // Create an originAttributes object by inIsolatedMozBrowser.
139   // Then create the originSuffix string from this object.
140   OriginAttributes attrs(inIsolatedMozBrowser ? true : false);
141   nsAutoCString suffix;
142   attrs.CreateSuffix(suffix);
143 
144   RefPtr<nsVariant> outVar(new nsVariant());
145   rv = outVar->SetAsAUTF8String(suffix);
146   NS_ENSURE_SUCCESS(rv, rv);
147 
148   outVar.forget(aResult);
149   return NS_OK;
150 }
151 
152 class SetAppIdFromOriginAttributesSQLFunction final
153     : public mozIStorageFunction {
154   ~SetAppIdFromOriginAttributesSQLFunction() = default;
155 
156   NS_DECL_ISUPPORTS
157   NS_DECL_MOZISTORAGEFUNCTION
158 };
159 
160 NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
161 
162 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)163 SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
164     mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
165   nsresult rv;
166   nsAutoCString suffix;
167   OriginAttributes attrs;
168 
169   rv = aFunctionArguments->GetUTF8String(0, suffix);
170   NS_ENSURE_SUCCESS(rv, rv);
171   bool success = attrs.PopulateFromSuffix(suffix);
172   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
173 
174   RefPtr<nsVariant> outVar(new nsVariant());
175   rv = outVar->SetAsInt32(0);  // deprecated appId!
176   NS_ENSURE_SUCCESS(rv, rv);
177 
178   outVar.forget(aResult);
179   return NS_OK;
180 }
181 
182 class SetInBrowserFromOriginAttributesSQLFunction final
183     : public mozIStorageFunction {
184   ~SetInBrowserFromOriginAttributesSQLFunction() = default;
185 
186   NS_DECL_ISUPPORTS
187   NS_DECL_MOZISTORAGEFUNCTION
188 };
189 
190 NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
191                   mozIStorageFunction);
192 
193 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aFunctionArguments,nsIVariant ** aResult)194 SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
195     mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
196   nsresult rv;
197   nsAutoCString suffix;
198   OriginAttributes attrs;
199 
200   rv = aFunctionArguments->GetUTF8String(0, suffix);
201   NS_ENSURE_SUCCESS(rv, rv);
202   bool success = attrs.PopulateFromSuffix(suffix);
203   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
204 
205   RefPtr<nsVariant> outVar(new nsVariant());
206   rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
207   NS_ENSURE_SUCCESS(rv, rv);
208 
209   outVar.forget(aResult);
210   return NS_OK;
211 }
212 
213 /******************************************************************************
214  * DBListenerErrorHandler impl:
215  * Parent class for our async storage listeners that handles the logging of
216  * errors.
217  ******************************************************************************/
218 class DBListenerErrorHandler : public mozIStorageStatementCallback {
219  protected:
DBListenerErrorHandler(CookiePersistentStorage * dbState)220   explicit DBListenerErrorHandler(CookiePersistentStorage* dbState)
221       : mStorage(dbState) {}
222   RefPtr<CookiePersistentStorage> mStorage;
223   virtual const char* GetOpType() = 0;
224 
225  public:
HandleError(mozIStorageError * aError)226   NS_IMETHOD HandleError(mozIStorageError* aError) override {
227     if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
228       int32_t result = -1;
229       aError->GetResult(&result);
230 
231       nsAutoCString message;
232       aError->GetMessage(message);
233       COOKIE_LOGSTRING(
234           LogLevel::Warning,
235           ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
236            "performing operation '%s' with message '%s'; rebuilding database.",
237            result, GetOpType(), message.get()));
238     }
239 
240     // Rebuild the database.
241     mStorage->HandleCorruptDB();
242 
243     return NS_OK;
244   }
245 };
246 
247 /******************************************************************************
248  * InsertCookieDBListener impl:
249  * mozIStorageStatementCallback used to track asynchronous insertion operations.
250  ******************************************************************************/
251 class InsertCookieDBListener final : public DBListenerErrorHandler {
252  private:
GetOpType()253   const char* GetOpType() override { return "INSERT"; }
254 
255   ~InsertCookieDBListener() = default;
256 
257  public:
258   NS_DECL_ISUPPORTS
259 
InsertCookieDBListener(CookiePersistentStorage * dbState)260   explicit InsertCookieDBListener(CookiePersistentStorage* dbState)
261       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)262   NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
263     MOZ_ASSERT_UNREACHABLE(
264         "Unexpected call to "
265         "InsertCookieDBListener::HandleResult");
266     return NS_OK;
267   }
HandleCompletion(uint16_t aReason)268   NS_IMETHOD HandleCompletion(uint16_t aReason) override {
269     // If we were rebuilding the db and we succeeded, make our mCorruptFlag say
270     // so.
271     if (mStorage->GetCorruptFlag() == CookiePersistentStorage::REBUILDING &&
272         aReason == mozIStorageStatementCallback::REASON_FINISHED) {
273       COOKIE_LOGSTRING(
274           LogLevel::Debug,
275           ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
276       mStorage->SetCorruptFlag(CookiePersistentStorage::OK);
277     }
278 
279     // This notification is just for testing.
280     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
281     if (os) {
282       os->NotifyObservers(nullptr, "cookie-saved-on-disk", nullptr);
283     }
284 
285     return NS_OK;
286   }
287 };
288 
289 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
290 
291 /******************************************************************************
292  * UpdateCookieDBListener impl:
293  * mozIStorageStatementCallback used to track asynchronous update operations.
294  ******************************************************************************/
295 class UpdateCookieDBListener final : public DBListenerErrorHandler {
296  private:
GetOpType()297   const char* GetOpType() override { return "UPDATE"; }
298 
299   ~UpdateCookieDBListener() = default;
300 
301  public:
302   NS_DECL_ISUPPORTS
303 
UpdateCookieDBListener(CookiePersistentStorage * dbState)304   explicit UpdateCookieDBListener(CookiePersistentStorage* dbState)
305       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)306   NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
307     MOZ_ASSERT_UNREACHABLE(
308         "Unexpected call to "
309         "UpdateCookieDBListener::HandleResult");
310     return NS_OK;
311   }
HandleCompletion(uint16_t)312   NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
313 };
314 
315 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
316 
317 /******************************************************************************
318  * RemoveCookieDBListener impl:
319  * mozIStorageStatementCallback used to track asynchronous removal operations.
320  ******************************************************************************/
321 class RemoveCookieDBListener final : public DBListenerErrorHandler {
322  private:
GetOpType()323   const char* GetOpType() override { return "REMOVE"; }
324 
325   ~RemoveCookieDBListener() = default;
326 
327  public:
328   NS_DECL_ISUPPORTS
329 
RemoveCookieDBListener(CookiePersistentStorage * dbState)330   explicit RemoveCookieDBListener(CookiePersistentStorage* dbState)
331       : DBListenerErrorHandler(dbState) {}
HandleResult(mozIStorageResultSet *)332   NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
333     MOZ_ASSERT_UNREACHABLE(
334         "Unexpected call to "
335         "RemoveCookieDBListener::HandleResult");
336     return NS_OK;
337   }
HandleCompletion(uint16_t)338   NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
339 };
340 
341 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
342 
343 /******************************************************************************
344  * CloseCookieDBListener imp:
345  * Static mozIStorageCompletionCallback used to notify when the database is
346  * successfully closed.
347  ******************************************************************************/
348 class CloseCookieDBListener final : public mozIStorageCompletionCallback {
349   ~CloseCookieDBListener() = default;
350 
351  public:
CloseCookieDBListener(CookiePersistentStorage * dbState)352   explicit CloseCookieDBListener(CookiePersistentStorage* dbState)
353       : mStorage(dbState) {}
354   RefPtr<CookiePersistentStorage> mStorage;
355   NS_DECL_ISUPPORTS
356 
Complete(nsresult,nsISupports *)357   NS_IMETHOD Complete(nsresult /*status*/, nsISupports* /*value*/) override {
358     mStorage->HandleDBClosed();
359     return NS_OK;
360   }
361 };
362 
363 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
364 
365 }  // namespace
366 
367 // static
Create()368 already_AddRefed<CookiePersistentStorage> CookiePersistentStorage::Create() {
369   RefPtr<CookiePersistentStorage> storage = new CookiePersistentStorage();
370   storage->Init();
371 
372   return storage.forget();
373 }
374 
CookiePersistentStorage()375 CookiePersistentStorage::CookiePersistentStorage()
376     : mMonitor("CookiePersistentStorage"),
377       mInitialized(false),
378       mCorruptFlag(OK) {}
379 
NotifyChangedInternal(nsISupports * aSubject,const char16_t * aData,bool aOldCookieIsSession)380 void CookiePersistentStorage::NotifyChangedInternal(nsISupports* aSubject,
381                                                     const char16_t* aData,
382                                                     bool aOldCookieIsSession) {
383   // Notify for topic "session-cookie-changed" to update the copy of session
384   // cookies in session restore component.
385 
386   // Filter out notifications for individual non-session cookies.
387   if (NS_LITERAL_STRING("changed").Equals(aData) ||
388       NS_LITERAL_STRING("deleted").Equals(aData) ||
389       NS_LITERAL_STRING("added").Equals(aData)) {
390     nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
391     MOZ_ASSERT(xpcCookie);
392     auto cookie = static_cast<Cookie*>(xpcCookie.get());
393     if (!cookie->IsSession() && !aOldCookieIsSession) {
394       return;
395     }
396   }
397 
398   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
399   if (os) {
400     os->NotifyObservers(aSubject, "session-cookie-changed", aData);
401   }
402 }
403 
RemoveAllInternal()404 void CookiePersistentStorage::RemoveAllInternal() {
405   // clear the cookie file
406   if (mDBConn) {
407     nsCOMPtr<mozIStorageAsyncStatement> stmt;
408     nsresult rv = mDBConn->CreateAsyncStatement(
409         NS_LITERAL_CSTRING("DELETE FROM moz_cookies"), getter_AddRefs(stmt));
410     if (NS_SUCCEEDED(rv)) {
411       nsCOMPtr<mozIStoragePendingStatement> handle;
412       rv = stmt->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
413       MOZ_ASSERT(NS_SUCCEEDED(rv));
414     } else {
415       // Recreate the database.
416       COOKIE_LOGSTRING(LogLevel::Debug,
417                        ("RemoveAll(): corruption detected with rv 0x%" PRIx32,
418                         static_cast<uint32_t>(rv)));
419       HandleCorruptDB();
420     }
421   }
422 }
423 
HandleCorruptDB()424 void CookiePersistentStorage::HandleCorruptDB() {
425   COOKIE_LOGSTRING(LogLevel::Debug,
426                    ("HandleCorruptDB(): CookieStorage %p has mCorruptFlag %u",
427                     this, mCorruptFlag));
428 
429   // Mark the database corrupt, so the close listener can begin reconstructing
430   // it.
431   switch (mCorruptFlag) {
432     case OK: {
433       // Move to 'closing' state.
434       mCorruptFlag = CLOSING_FOR_REBUILD;
435 
436       CleanupCachedStatements();
437       mDBConn->AsyncClose(mCloseListener);
438       CleanupDBConnection();
439       break;
440     }
441     case CLOSING_FOR_REBUILD: {
442       // We had an error while waiting for close completion. That's OK, just
443       // ignore it -- we're rebuilding anyway.
444       return;
445     }
446     case REBUILDING: {
447       // We had an error while rebuilding the DB. Game over. Close the database
448       // and let the close handler do nothing; then we'll move it out of the
449       // way.
450       CleanupCachedStatements();
451       if (mDBConn) {
452         mDBConn->AsyncClose(mCloseListener);
453       }
454       CleanupDBConnection();
455       break;
456     }
457   }
458 }
459 
RemoveCookiesWithOriginAttributes(const OriginAttributesPattern & aPattern,const nsACString & aBaseDomain)460 void CookiePersistentStorage::RemoveCookiesWithOriginAttributes(
461     const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
462   mozStorageTransaction transaction(mDBConn, false);
463 
464   CookieStorage::RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
465 
466   DebugOnly<nsresult> rv = transaction.Commit();
467   MOZ_ASSERT(NS_SUCCEEDED(rv));
468 }
469 
RemoveCookiesFromExactHost(const nsACString & aHost,const nsACString & aBaseDomain,const OriginAttributesPattern & aPattern)470 void CookiePersistentStorage::RemoveCookiesFromExactHost(
471     const nsACString& aHost, const nsACString& aBaseDomain,
472     const OriginAttributesPattern& aPattern) {
473   mozStorageTransaction transaction(mDBConn, false);
474 
475   CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern);
476 
477   DebugOnly<nsresult> rv = transaction.Commit();
478   MOZ_ASSERT(NS_SUCCEEDED(rv));
479 }
480 
RemoveCookieFromDB(const CookieListIter & aIter)481 void CookiePersistentStorage::RemoveCookieFromDB(const CookieListIter& aIter) {
482   // if it's a non-session cookie, remove it from the db
483   if (aIter.Cookie()->IsSession() || !mDBConn) {
484     return;
485   }
486 
487   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
488   mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
489 
490   PrepareCookieRemoval(aIter, paramsArray);
491 
492   DebugOnly<nsresult> rv = mStmtDelete->BindParameters(paramsArray);
493   MOZ_ASSERT(NS_SUCCEEDED(rv));
494 
495   nsCOMPtr<mozIStoragePendingStatement> handle;
496   rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
497   MOZ_ASSERT(NS_SUCCEEDED(rv));
498 }
499 
PrepareCookieRemoval(const CookieListIter & aIter,mozIStorageBindingParamsArray * aParamsArray)500 void CookiePersistentStorage::PrepareCookieRemoval(
501     const CookieListIter& aIter, mozIStorageBindingParamsArray* aParamsArray) {
502   // if it's a non-session cookie, remove it from the db
503   if (aIter.Cookie()->IsSession() || !mDBConn) {
504     return;
505   }
506 
507   nsCOMPtr<mozIStorageBindingParams> params;
508   aParamsArray->NewBindingParams(getter_AddRefs(params));
509 
510   DebugOnly<nsresult> rv = params->BindUTF8StringByName(
511       NS_LITERAL_CSTRING("name"), aIter.Cookie()->Name());
512   MOZ_ASSERT(NS_SUCCEEDED(rv));
513 
514   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
515                                     aIter.Cookie()->Host());
516   MOZ_ASSERT(NS_SUCCEEDED(rv));
517 
518   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
519                                     aIter.Cookie()->Path());
520   MOZ_ASSERT(NS_SUCCEEDED(rv));
521 
522   nsAutoCString suffix;
523   aIter.Cookie()->OriginAttributesRef().CreateSuffix(suffix);
524   rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
525                                     suffix);
526   MOZ_ASSERT(NS_SUCCEEDED(rv));
527 
528   rv = aParamsArray->AddParams(params);
529   MOZ_ASSERT(NS_SUCCEEDED(rv));
530 }
531 
532 // Null out the statements.
533 // This must be done before closing the connection.
CleanupCachedStatements()534 void CookiePersistentStorage::CleanupCachedStatements() {
535   mStmtInsert = nullptr;
536   mStmtDelete = nullptr;
537   mStmtUpdate = nullptr;
538 }
539 
540 // Null out the listeners, and the database connection itself. This
541 // will not null out the statements, cancel a pending read or
542 // asynchronously close the connection -- these must be done
543 // beforehand if necessary.
CleanupDBConnection()544 void CookiePersistentStorage::CleanupDBConnection() {
545   MOZ_ASSERT(!mStmtInsert, "mStmtInsert has been cleaned up");
546   MOZ_ASSERT(!mStmtDelete, "mStmtDelete has been cleaned up");
547   MOZ_ASSERT(!mStmtUpdate, "mStmtUpdate has been cleaned up");
548 
549   // Null out the database connections. If 'mDBConn' has not been used for any
550   // asynchronous operations yet, this will synchronously close it; otherwise,
551   // it's expected that the caller has performed an AsyncClose prior.
552   mDBConn = nullptr;
553 
554   // Manually null out our listeners. This is necessary because they hold a
555   // strong ref to the CookieStorage itself. They'll stay alive until whatever
556   // statements are still executing complete.
557   mInsertListener = nullptr;
558   mUpdateListener = nullptr;
559   mRemoveListener = nullptr;
560   mCloseListener = nullptr;
561 }
562 
Close()563 void CookiePersistentStorage::Close() {
564   if (mThread) {
565     mThread->Shutdown();
566     mThread = nullptr;
567   }
568 
569   // Cleanup cached statements before we can close anything.
570   CleanupCachedStatements();
571 
572   if (mDBConn) {
573     // Asynchronously close the connection. We will null it below.
574     mDBConn->AsyncClose(mCloseListener);
575   }
576 
577   CleanupDBConnection();
578 
579   mInitialized = false;
580   mInitializedDBConn = false;
581 }
582 
StoreCookie(const nsACString & aBaseDomain,const OriginAttributes & aOriginAttributes,Cookie * aCookie)583 void CookiePersistentStorage::StoreCookie(
584     const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
585     Cookie* aCookie) {
586   // if it's a non-session cookie and hasn't just been read from the db, write
587   // it out.
588   if (aCookie->IsSession() || !mDBConn) {
589     return;
590   }
591 
592   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
593   mStmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
594 
595   CookieKey key(aBaseDomain, aOriginAttributes);
596   BindCookieParameters(paramsArray, key, aCookie);
597 
598   MaybeStoreCookiesToDB(paramsArray);
599 }
600 
MaybeStoreCookiesToDB(mozIStorageBindingParamsArray * aParamsArray)601 void CookiePersistentStorage::MaybeStoreCookiesToDB(
602     mozIStorageBindingParamsArray* aParamsArray) {
603   if (!aParamsArray) {
604     return;
605   }
606 
607   uint32_t length;
608   aParamsArray->GetLength(&length);
609   if (!length) {
610     return;
611   }
612 
613   DebugOnly<nsresult> rv = mStmtInsert->BindParameters(aParamsArray);
614   MOZ_ASSERT(NS_SUCCEEDED(rv));
615 
616   nsCOMPtr<mozIStoragePendingStatement> handle;
617   rv = mStmtInsert->ExecuteAsync(mInsertListener, getter_AddRefs(handle));
618   MOZ_ASSERT(NS_SUCCEEDED(rv));
619 }
620 
StaleCookies(const nsTArray<Cookie * > & aCookieList,int64_t aCurrentTimeInUsec)621 void CookiePersistentStorage::StaleCookies(const nsTArray<Cookie*>& aCookieList,
622                                            int64_t aCurrentTimeInUsec) {
623   // Create an array of parameters to bind to our update statement. Batching
624   // is OK here since we're updating cookies with no interleaved operations.
625   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
626   mozIStorageAsyncStatement* stmt = mStmtUpdate;
627   if (mDBConn) {
628     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
629   }
630 
631   int32_t count = aCookieList.Length();
632   for (int32_t i = 0; i < count; ++i) {
633     Cookie* cookie = aCookieList.ElementAt(i);
634 
635     if (cookie->IsStale()) {
636       UpdateCookieInList(cookie, aCurrentTimeInUsec, paramsArray);
637     }
638   }
639   // Update the database now if necessary.
640   if (paramsArray) {
641     uint32_t length;
642     paramsArray->GetLength(&length);
643     if (length) {
644       DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
645       MOZ_ASSERT(NS_SUCCEEDED(rv));
646 
647       nsCOMPtr<mozIStoragePendingStatement> handle;
648       rv = stmt->ExecuteAsync(mUpdateListener, getter_AddRefs(handle));
649       MOZ_ASSERT(NS_SUCCEEDED(rv));
650     }
651   }
652 }
653 
UpdateCookieInList(Cookie * aCookie,int64_t aLastAccessed,mozIStorageBindingParamsArray * aParamsArray)654 void CookiePersistentStorage::UpdateCookieInList(
655     Cookie* aCookie, int64_t aLastAccessed,
656     mozIStorageBindingParamsArray* aParamsArray) {
657   MOZ_ASSERT(aCookie);
658 
659   // udpate the lastAccessed timestamp
660   aCookie->SetLastAccessed(aLastAccessed);
661 
662   // if it's a non-session cookie, update it in the db too
663   if (!aCookie->IsSession() && aParamsArray) {
664     // Create our params holder.
665     nsCOMPtr<mozIStorageBindingParams> params;
666     aParamsArray->NewBindingParams(getter_AddRefs(params));
667 
668     // Bind our parameters.
669     DebugOnly<nsresult> rv = params->BindInt64ByName(
670         NS_LITERAL_CSTRING("lastAccessed"), aLastAccessed);
671     MOZ_ASSERT(NS_SUCCEEDED(rv));
672 
673     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
674                                       aCookie->Name());
675     MOZ_ASSERT(NS_SUCCEEDED(rv));
676 
677     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
678                                       aCookie->Host());
679     MOZ_ASSERT(NS_SUCCEEDED(rv));
680 
681     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
682                                       aCookie->Path());
683     MOZ_ASSERT(NS_SUCCEEDED(rv));
684 
685     nsAutoCString suffix;
686     aCookie->OriginAttributesRef().CreateSuffix(suffix);
687     rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
688                                       suffix);
689     MOZ_ASSERT(NS_SUCCEEDED(rv));
690 
691     // Add our bound parameters to the array.
692     rv = aParamsArray->AddParams(params);
693     MOZ_ASSERT(NS_SUCCEEDED(rv));
694   }
695 }
696 
DeleteFromDB(mozIStorageBindingParamsArray * aParamsArray)697 void CookiePersistentStorage::DeleteFromDB(
698     mozIStorageBindingParamsArray* aParamsArray) {
699   uint32_t length;
700   aParamsArray->GetLength(&length);
701   if (length) {
702     DebugOnly<nsresult> rv = mStmtDelete->BindParameters(aParamsArray);
703     MOZ_ASSERT(NS_SUCCEEDED(rv));
704 
705     nsCOMPtr<mozIStoragePendingStatement> handle;
706     rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
707     MOZ_ASSERT(NS_SUCCEEDED(rv));
708   }
709 }
710 
Activate()711 void CookiePersistentStorage::Activate() {
712   MOZ_ASSERT(!mThread, "already have a cookie thread");
713 
714   mStorageService = do_GetService("@mozilla.org/storage/service;1");
715   MOZ_ASSERT(mStorageService);
716 
717   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
718   MOZ_ASSERT(mTLDService);
719 
720   // Get our cookie file.
721   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
722                                        getter_AddRefs(mCookieFile));
723   if (NS_FAILED(rv)) {
724     // We've already set up our CookieStorages appropriately; nothing more to
725     // do.
726     COOKIE_LOGSTRING(LogLevel::Warning,
727                      ("InitCookieStorages(): couldn't get cookie file"));
728 
729     mInitializedDBConn = true;
730     mInitialized = true;
731     return;
732   }
733 
734   mCookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
735 
736   NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread)));
737 
738   RefPtr<CookiePersistentStorage> self = this;
739   nsCOMPtr<nsIRunnable> runnable =
740       NS_NewRunnableFunction("CookiePersistentStorage::Activate", [self] {
741         MonitorAutoLock lock(self->mMonitor);
742 
743         // Attempt to open and read the database. If TryInitDB() returns
744         // RESULT_RETRY, do so.
745         OpenDBResult result = self->TryInitDB(false);
746         if (result == RESULT_RETRY) {
747           // Database may be corrupt. Synchronously close the connection, clean
748           // up the default CookieStorage, and try again.
749           COOKIE_LOGSTRING(LogLevel::Warning,
750                            ("InitCookieStorages(): retrying TryInitDB()"));
751           self->CleanupCachedStatements();
752           self->CleanupDBConnection();
753           result = self->TryInitDB(true);
754           if (result == RESULT_RETRY) {
755             // We're done. Change the code to failure so we clean up below.
756             result = RESULT_FAILURE;
757           }
758         }
759 
760         if (result == RESULT_FAILURE) {
761           COOKIE_LOGSTRING(
762               LogLevel::Warning,
763               ("InitCookieStorages(): TryInitDB() failed, closing connection"));
764 
765           // Connection failure is unrecoverable. Clean up our connection. We
766           // can run fine without persistent storage -- e.g. if there's no
767           // profile.
768           self->CleanupCachedStatements();
769           self->CleanupDBConnection();
770 
771           // No need to initialize mDBConn
772           self->mInitializedDBConn = true;
773         }
774 
775         self->mInitialized = true;
776 
777         NS_DispatchToMainThread(
778             NS_NewRunnableFunction("CookiePersistentStorage::InitDBConn",
779                                    [self] { self->InitDBConn(); }));
780         self->mMonitor.Notify();
781       });
782 
783   mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
784 }
785 
786 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
787  * move the existing database file out of the way and create a new one.
788  *
789  * @returns RESULT_OK if opening or creating the database succeeded;
790  *          RESULT_RETRY if the database cannot be opened, is corrupt, or some
791  *          other failure occurred that might be resolved by recreating the
792  *          database; or RESULT_FAILED if there was an unrecoverable error and
793  *          we must run without a database.
794  *
795  * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
796  * cleanup of the default CookieStorage.
797  */
TryInitDB(bool aRecreateDB)798 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::TryInitDB(
799     bool aRecreateDB) {
800   NS_ASSERTION(!mDBConn, "nonnull mDBConn");
801   NS_ASSERTION(!mStmtInsert, "nonnull mStmtInsert");
802   NS_ASSERTION(!mInsertListener, "nonnull mInsertListener");
803   NS_ASSERTION(!mSyncConn, "nonnull mSyncConn");
804   NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
805 
806   // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
807   // want to delete it outright, since it may be useful for debugging purposes,
808   // so we move it out of the way.
809   nsresult rv;
810   if (aRecreateDB) {
811     nsCOMPtr<nsIFile> backupFile;
812     mCookieFile->Clone(getter_AddRefs(backupFile));
813     rv = backupFile->MoveToNative(nullptr,
814                                   NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
815     NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
816   }
817 
818   // This block provides scope for the Telemetry AutoTimer
819   {
820     Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
821         telemetry;
822     ReadAheadFile(mCookieFile);
823 
824     // open a connection to the cookie database, and only cache our connection
825     // and statements upon success. The connection is opened unshared to
826     // eliminate cache contention between the main and background threads.
827     rv = mStorageService->OpenUnsharedDatabase(mCookieFile,
828                                                getter_AddRefs(mSyncConn));
829     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
830   }
831 
832   auto guard = MakeScopeExit([&] { mSyncConn = nullptr; });
833 
834   bool tableExists = false;
835   mSyncConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"), &tableExists);
836   if (!tableExists) {
837     rv = CreateTable();
838     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
839 
840   } else {
841     // table already exists; check the schema version before reading
842     int32_t dbSchemaVersion;
843     rv = mSyncConn->GetSchemaVersion(&dbSchemaVersion);
844     NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
845 
846     // Start a transaction for the whole migration block.
847     mozStorageTransaction transaction(mSyncConn, true);
848 
849     switch (dbSchemaVersion) {
850       // Upgrading.
851       // Every time you increment the database schema, you need to implement
852       // the upgrading code from the previous version to the new one. If
853       // migration fails for any reason, it's a bug -- so we return RESULT_RETRY
854       // such that the original database will be saved, in the hopes that we
855       // might one day see it and fix it.
856       case 1: {
857         // Add the lastAccessed column to the table.
858         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
859             "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
860         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
861       }
862         // Fall through to the next upgrade.
863         [[fallthrough]];
864 
865       case 2: {
866         // Add the baseDomain column and index to the table.
867         rv = mSyncConn->ExecuteSimpleSQL(
868             NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD baseDomain TEXT"));
869         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
870 
871         // Compute the baseDomains for the table. This must be done eagerly
872         // otherwise we won't be able to synchronously read in individual
873         // domains on demand.
874         const int64_t SCHEMA2_IDX_ID = 0;
875         const int64_t SCHEMA2_IDX_HOST = 1;
876         nsCOMPtr<mozIStorageStatement> select;
877         rv = mSyncConn->CreateStatement(
878             NS_LITERAL_CSTRING("SELECT id, host FROM moz_cookies"),
879             getter_AddRefs(select));
880         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
881 
882         nsCOMPtr<mozIStorageStatement> update;
883         rv = mSyncConn->CreateStatement(
884             NS_LITERAL_CSTRING("UPDATE moz_cookies SET baseDomain = "
885                                ":baseDomain WHERE id = :id"),
886             getter_AddRefs(update));
887         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
888 
889         nsCString baseDomain;
890         nsCString host;
891         bool hasResult;
892         while (true) {
893           rv = select->ExecuteStep(&hasResult);
894           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
895 
896           if (!hasResult) {
897             break;
898           }
899 
900           int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
901           select->GetUTF8String(SCHEMA2_IDX_HOST, host);
902 
903           rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host,
904                                                     baseDomain);
905           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
906 
907           mozStorageStatementScoper scoper(update);
908 
909           rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
910                                             baseDomain);
911           MOZ_ASSERT(NS_SUCCEEDED(rv));
912           rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
913           MOZ_ASSERT(NS_SUCCEEDED(rv));
914 
915           rv = update->ExecuteStep(&hasResult);
916           NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
917         }
918 
919         // Create an index on baseDomain.
920         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
921             "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
922         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
923       }
924         // Fall through to the next upgrade.
925         [[fallthrough]];
926 
927       case 3: {
928         // Add the creationTime column to the table, and create a unique index
929         // on (name, host, path). Before we do this, we have to purge the table
930         // of expired cookies such that we know that the (name, host, path)
931         // index is truly unique -- otherwise we can't create the index. Note
932         // that we can't just execute a statement to delete all rows where the
933         // expiry column is in the past -- doing so would rely on the clock
934         // (both now and when previous cookies were set) being monotonic.
935 
936         // Select the whole table, and order by the fields we're interested in.
937         // This means we can simply do a linear traversal of the results and
938         // check for duplicates as we go.
939         const int64_t SCHEMA3_IDX_ID = 0;
940         const int64_t SCHEMA3_IDX_NAME = 1;
941         const int64_t SCHEMA3_IDX_HOST = 2;
942         const int64_t SCHEMA3_IDX_PATH = 3;
943         nsCOMPtr<mozIStorageStatement> select;
944         rv = mSyncConn->CreateStatement(
945             NS_LITERAL_CSTRING(
946                 "SELECT id, name, host, path FROM moz_cookies "
947                 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
948             getter_AddRefs(select));
949         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
950 
951         nsCOMPtr<mozIStorageStatement> deleteExpired;
952         rv = mSyncConn->CreateStatement(
953             NS_LITERAL_CSTRING("DELETE FROM moz_cookies WHERE id = :id"),
954             getter_AddRefs(deleteExpired));
955         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
956 
957         // Read the first row.
958         bool hasResult;
959         rv = select->ExecuteStep(&hasResult);
960         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
961 
962         if (hasResult) {
963           nsCString name1;
964           nsCString host1;
965           nsCString path1;
966           int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
967           select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
968           select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
969           select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
970 
971           nsCString name2;
972           nsCString host2;
973           nsCString path2;
974           while (true) {
975             // Read the second row.
976             rv = select->ExecuteStep(&hasResult);
977             NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
978 
979             if (!hasResult) {
980               break;
981             }
982 
983             int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
984             select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
985             select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
986             select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
987 
988             // If the two rows match in (name, host, path), we know the earlier
989             // row has an earlier expiry time. Delete it.
990             if (name1 == name2 && host1 == host2 && path1 == path2) {
991               mozStorageStatementScoper scoper(deleteExpired);
992 
993               rv =
994                   deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"), id1);
995               MOZ_ASSERT(NS_SUCCEEDED(rv));
996 
997               rv = deleteExpired->ExecuteStep(&hasResult);
998               NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
999             }
1000 
1001             // Make the second row the first for the next iteration.
1002             name1 = name2;
1003             host1 = host2;
1004             path1 = path2;
1005             id1 = id2;
1006           }
1007         }
1008 
1009         // Add the creationTime column to the table.
1010         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1011             "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
1012         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1013 
1014         // Copy the id of each row into the new creationTime column.
1015         rv = mSyncConn->ExecuteSimpleSQL(
1016             NS_LITERAL_CSTRING("UPDATE moz_cookies SET creationTime = "
1017                                "(SELECT id WHERE id = moz_cookies.id)"));
1018         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1019 
1020         // Create a unique index on (name, host, path) to allow fast lookup.
1021         rv = mSyncConn->ExecuteSimpleSQL(
1022             NS_LITERAL_CSTRING("CREATE UNIQUE INDEX moz_uniqueid "
1023                                "ON moz_cookies (name, host, path)"));
1024         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1025       }
1026         // Fall through to the next upgrade.
1027         [[fallthrough]];
1028 
1029       case 4: {
1030         // We need to add appId/inBrowserElement, plus change a constraint on
1031         // the table (unique entries now include appId/inBrowserElement):
1032         // this requires creating a new table and copying the data to it.  We
1033         // then rename the new table to the old name.
1034         //
1035         // Why we made this change: appId/inBrowserElement allow "cookie jars"
1036         // for Firefox OS. We create a separate cookie namespace per {appId,
1037         // inBrowserElement}.  When upgrading, we convert existing cookies
1038         // (which imply we're on desktop/mobile) to use {0, false}, as that is
1039         // the only namespace used by a non-Firefox-OS implementation.
1040 
1041         // Rename existing table
1042         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1043             "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1044         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1045 
1046         // Drop existing index (CreateTable will create new one for new table)
1047         rv = mSyncConn->ExecuteSimpleSQL(
1048             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1049         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1050 
1051         // Create new table (with new fields and new unique constraint)
1052         rv = CreateTableForSchemaVersion5();
1053         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1054 
1055         // Copy data from old table, using appId/inBrowser=0 for existing rows
1056         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1057             "INSERT INTO moz_cookies "
1058             "(baseDomain, appId, inBrowserElement, name, value, host, path, "
1059             "expiry,"
1060             " lastAccessed, creationTime, isSecure, isHttpOnly) "
1061             "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
1062             " lastAccessed, creationTime, isSecure, isHttpOnly "
1063             "FROM moz_cookies_old"));
1064         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1065 
1066         // Drop old table
1067         rv = mSyncConn->ExecuteSimpleSQL(
1068             NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old"));
1069         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1070 
1071         COOKIE_LOGSTRING(LogLevel::Debug,
1072                          ("Upgraded database to schema version 5"));
1073       }
1074         // Fall through to the next upgrade.
1075         [[fallthrough]];
1076 
1077       case 5: {
1078         // Change in the version: Replace the columns |appId| and
1079         // |inBrowserElement| by a single column |originAttributes|.
1080         //
1081         // Why we made this change: FxOS new security model (NSec) encapsulates
1082         // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to
1083         // make it easier to modify the contents of this structure in the
1084         // future.
1085         //
1086         // We do the migration in several steps:
1087         // 1. Rename the old table.
1088         // 2. Create a new table.
1089         // 3. Copy data from the old table to the new table; convert appId and
1090         //    inBrowserElement to originAttributes in the meantime.
1091 
1092         // Rename existing table.
1093         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1094             "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1095         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1096 
1097         // Drop existing index (CreateTable will create new one for new table).
1098         rv = mSyncConn->ExecuteSimpleSQL(
1099             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1100         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1101 
1102         // Create new table with new fields and new unique constraint.
1103         rv = CreateTableForSchemaVersion6();
1104         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1105 
1106         // Copy data from old table without the two deprecated columns appId and
1107         // inBrowserElement.
1108         nsCOMPtr<mozIStorageFunction> convertToOriginAttrs(
1109             new ConvertAppIdToOriginAttrsSQLFunction());
1110         NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
1111 
1112         NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
1113                                  "CONVERT_TO_ORIGIN_ATTRIBUTES");
1114 
1115         rv = mSyncConn->CreateFunction(convertToOriginAttrsName, 2,
1116                                        convertToOriginAttrs);
1117         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1118 
1119         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1120             "INSERT INTO moz_cookies "
1121             "(baseDomain, originAttributes, name, value, host, path, expiry,"
1122             " lastAccessed, creationTime, isSecure, isHttpOnly) "
1123             "SELECT baseDomain, "
1124             " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
1125             " name, value, host, path, expiry, lastAccessed, creationTime, "
1126             " isSecure, isHttpOnly "
1127             "FROM moz_cookies_old"));
1128         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1129 
1130         rv = mSyncConn->RemoveFunction(convertToOriginAttrsName);
1131         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1132 
1133         // Drop old table
1134         rv = mSyncConn->ExecuteSimpleSQL(
1135             NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old"));
1136         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1137 
1138         COOKIE_LOGSTRING(LogLevel::Debug,
1139                          ("Upgraded database to schema version 6"));
1140       }
1141         [[fallthrough]];
1142 
1143       case 6: {
1144         // We made a mistake in schema version 6. We cannot remove expected
1145         // columns of any version (checked in the default case) from cookie
1146         // database, because doing this would destroy the possibility of
1147         // downgrading database.
1148         //
1149         // This version simply restores appId and inBrowserElement columns in
1150         // order to fix downgrading issue even though these two columns are no
1151         // longer used in the latest schema.
1152         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1153             "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
1154         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1155 
1156         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1157             "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
1158         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1159 
1160         // Compute and populate the values of appId and inBrwoserElement from
1161         // originAttributes.
1162         nsCOMPtr<mozIStorageFunction> setAppId(
1163             new SetAppIdFromOriginAttributesSQLFunction());
1164         NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
1165 
1166         NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
1167 
1168         rv = mSyncConn->CreateFunction(setAppIdName, 1, setAppId);
1169         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1170 
1171         nsCOMPtr<mozIStorageFunction> setInBrowser(
1172             new SetInBrowserFromOriginAttributesSQLFunction());
1173         NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
1174 
1175         NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
1176 
1177         rv = mSyncConn->CreateFunction(setInBrowserName, 1, setInBrowser);
1178         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1179 
1180         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1181             "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
1182             "inBrowserElement = SET_IN_BROWSER(originAttributes);"));
1183         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1184 
1185         rv = mSyncConn->RemoveFunction(setAppIdName);
1186         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1187 
1188         rv = mSyncConn->RemoveFunction(setInBrowserName);
1189         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1190 
1191         COOKIE_LOGSTRING(LogLevel::Debug,
1192                          ("Upgraded database to schema version 7"));
1193       }
1194         [[fallthrough]];
1195 
1196       case 7: {
1197         // Remove the appId field from moz_cookies.
1198         //
1199         // Unfortunately sqlite doesn't support dropping columns using ALTER
1200         // TABLE, so we need to go through the procedure documented in
1201         // https://www.sqlite.org/lang_altertable.html.
1202 
1203         // Drop existing index
1204         rv = mSyncConn->ExecuteSimpleSQL(
1205             NS_LITERAL_CSTRING("DROP INDEX moz_basedomain"));
1206         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1207 
1208         // Create a new_moz_cookies table without the appId field.
1209         rv = mSyncConn->ExecuteSimpleSQL(
1210             NS_LITERAL_CSTRING("CREATE TABLE new_moz_cookies("
1211                                "id INTEGER PRIMARY KEY, "
1212                                "baseDomain TEXT, "
1213                                "originAttributes TEXT NOT NULL DEFAULT '', "
1214                                "name TEXT, "
1215                                "value TEXT, "
1216                                "host TEXT, "
1217                                "path TEXT, "
1218                                "expiry INTEGER, "
1219                                "lastAccessed INTEGER, "
1220                                "creationTime INTEGER, "
1221                                "isSecure INTEGER, "
1222                                "isHttpOnly INTEGER, "
1223                                "inBrowserElement INTEGER DEFAULT 0, "
1224                                "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
1225                                "path, originAttributes)"
1226                                ")"));
1227         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1228 
1229         // Move the data over.
1230         rv = mSyncConn->ExecuteSimpleSQL(
1231             NS_LITERAL_CSTRING("INSERT INTO new_moz_cookies ("
1232                                "id, "
1233                                "baseDomain, "
1234                                "originAttributes, "
1235                                "name, "
1236                                "value, "
1237                                "host, "
1238                                "path, "
1239                                "expiry, "
1240                                "lastAccessed, "
1241                                "creationTime, "
1242                                "isSecure, "
1243                                "isHttpOnly, "
1244                                "inBrowserElement "
1245                                ") SELECT "
1246                                "id, "
1247                                "baseDomain, "
1248                                "originAttributes, "
1249                                "name, "
1250                                "value, "
1251                                "host, "
1252                                "path, "
1253                                "expiry, "
1254                                "lastAccessed, "
1255                                "creationTime, "
1256                                "isSecure, "
1257                                "isHttpOnly, "
1258                                "inBrowserElement "
1259                                "FROM moz_cookies;"));
1260         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1261 
1262         // Drop the old table
1263         rv = mSyncConn->ExecuteSimpleSQL(
1264             NS_LITERAL_CSTRING("DROP TABLE moz_cookies;"));
1265         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1266 
1267         // Rename new_moz_cookies to moz_cookies.
1268         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1269             "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
1270         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1271 
1272         // Recreate our index.
1273         rv = mSyncConn->ExecuteSimpleSQL(
1274             NS_LITERAL_CSTRING("CREATE INDEX moz_basedomain ON moz_cookies "
1275                                "(baseDomain, originAttributes)"));
1276         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1277 
1278         COOKIE_LOGSTRING(LogLevel::Debug,
1279                          ("Upgraded database to schema version 8"));
1280       }
1281         [[fallthrough]];
1282 
1283       case 8: {
1284         // Add the sameSite column to the table.
1285         rv = mSyncConn->ExecuteSimpleSQL(
1286             NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
1287         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1288 
1289         COOKIE_LOGSTRING(LogLevel::Debug,
1290                          ("Upgraded database to schema version 9"));
1291       }
1292         [[fallthrough]];
1293 
1294       case 9: {
1295         // Add the rawSameSite column to the table.
1296         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1297             "ALTER TABLE moz_cookies ADD rawSameSite INTEGER"));
1298         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1299 
1300         // Copy the current sameSite value into rawSameSite.
1301         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1302             "UPDATE moz_cookies SET rawSameSite = sameSite"));
1303         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1304 
1305         COOKIE_LOGSTRING(LogLevel::Debug,
1306                          ("Upgraded database to schema version 10"));
1307       }
1308         [[fallthrough]];
1309 
1310       case 10: {
1311         // Rename existing table
1312         rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1313             "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1314         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1315 
1316         // Create a new moz_cookies table without the baseDomain field.
1317         rv = mSyncConn->ExecuteSimpleSQL(
1318             NS_LITERAL_CSTRING("CREATE TABLE moz_cookies("
1319                                "id INTEGER PRIMARY KEY, "
1320                                "originAttributes TEXT NOT NULL DEFAULT '', "
1321                                "name TEXT, "
1322                                "value TEXT, "
1323                                "host TEXT, "
1324                                "path TEXT, "
1325                                "expiry INTEGER, "
1326                                "lastAccessed INTEGER, "
1327                                "creationTime INTEGER, "
1328                                "isSecure INTEGER, "
1329                                "isHttpOnly INTEGER, "
1330                                "inBrowserElement INTEGER DEFAULT 0, "
1331                                "sameSite INTEGER DEFAULT 0, "
1332                                "rawSameSite INTEGER DEFAULT 0, "
1333                                "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
1334                                "path, originAttributes)"
1335                                ")"));
1336         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1337 
1338         // Move the data over.
1339         rv = mSyncConn->ExecuteSimpleSQL(
1340             NS_LITERAL_CSTRING("INSERT INTO moz_cookies ("
1341                                "id, "
1342                                "originAttributes, "
1343                                "name, "
1344                                "value, "
1345                                "host, "
1346                                "path, "
1347                                "expiry, "
1348                                "lastAccessed, "
1349                                "creationTime, "
1350                                "isSecure, "
1351                                "isHttpOnly, "
1352                                "inBrowserElement, "
1353                                "sameSite, "
1354                                "rawSameSite "
1355                                ") SELECT "
1356                                "id, "
1357                                "originAttributes, "
1358                                "name, "
1359                                "value, "
1360                                "host, "
1361                                "path, "
1362                                "expiry, "
1363                                "lastAccessed, "
1364                                "creationTime, "
1365                                "isSecure, "
1366                                "isHttpOnly, "
1367                                "inBrowserElement, "
1368                                "sameSite, "
1369                                "rawSameSite "
1370                                "FROM moz_cookies_old "
1371                                "WHERE baseDomain NOTNULL;"));
1372         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1373 
1374         // Drop the old table
1375         rv = mSyncConn->ExecuteSimpleSQL(
1376             NS_LITERAL_CSTRING("DROP TABLE moz_cookies_old;"));
1377         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1378 
1379         // Drop the moz_basedomain index from the database (if it hasn't been
1380         // removed already by removing the table).
1381         rv = mSyncConn->ExecuteSimpleSQL(
1382             NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_basedomain;"));
1383         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1384 
1385         COOKIE_LOGSTRING(LogLevel::Debug,
1386                          ("Upgraded database to schema version 11"));
1387 
1388         // No more upgrades. Update the schema version.
1389         rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1390         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1391       }
1392         [[fallthrough]];
1393 
1394       case COOKIES_SCHEMA_VERSION:
1395         break;
1396 
1397       case 0: {
1398         NS_WARNING("couldn't get schema version!");
1399 
1400         // the table may be usable; someone might've just clobbered the schema
1401         // version. we can treat this case like a downgrade using the codepath
1402         // below, by verifying the columns we care about are all there. for now,
1403         // re-set the schema version in the db, in case the checks succeed (if
1404         // they don't, we're dropping the table anyway).
1405         rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1406         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1407       }
1408         // fall through to downgrade check
1409         [[fallthrough]];
1410 
1411       // downgrading.
1412       // if columns have been added to the table, we can still use the ones we
1413       // understand safely. if columns have been deleted or altered, just
1414       // blow away the table and start from scratch! if you change the way
1415       // a column is interpreted, make sure you also change its name so this
1416       // check will catch it.
1417       default: {
1418         // check if all the expected columns exist
1419         nsCOMPtr<mozIStorageStatement> stmt;
1420         rv = mSyncConn->CreateStatement(NS_LITERAL_CSTRING("SELECT "
1421                                                            "id, "
1422                                                            "originAttributes, "
1423                                                            "name, "
1424                                                            "value, "
1425                                                            "host, "
1426                                                            "path, "
1427                                                            "expiry, "
1428                                                            "lastAccessed, "
1429                                                            "creationTime, "
1430                                                            "isSecure, "
1431                                                            "isHttpOnly, "
1432                                                            "sameSite, "
1433                                                            "rawSameSite "
1434                                                            "FROM moz_cookies"),
1435                                         getter_AddRefs(stmt));
1436         if (NS_SUCCEEDED(rv)) {
1437           break;
1438         }
1439 
1440         // our columns aren't there - drop the table!
1441         rv = mSyncConn->ExecuteSimpleSQL(
1442             NS_LITERAL_CSTRING("DROP TABLE moz_cookies"));
1443         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1444 
1445         rv = CreateTable();
1446         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1447       } break;
1448     }
1449   }
1450 
1451   // if we deleted a corrupt db, don't attempt to import - return now
1452   if (aRecreateDB) {
1453     return RESULT_OK;
1454   }
1455 
1456   // check whether to import or just read in the db
1457   if (tableExists) {
1458     return Read();
1459   }
1460 
1461   return RESULT_OK;
1462 }
1463 
RebuildCorruptDB()1464 void CookiePersistentStorage::RebuildCorruptDB() {
1465   NS_ASSERTION(!mDBConn, "shouldn't have an open db connection");
1466   NS_ASSERTION(mCorruptFlag == CookiePersistentStorage::CLOSING_FOR_REBUILD,
1467                "should be in CLOSING_FOR_REBUILD state");
1468 
1469   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1470 
1471   mCorruptFlag = CookiePersistentStorage::REBUILDING;
1472 
1473   COOKIE_LOGSTRING(LogLevel::Debug,
1474                    ("RebuildCorruptDB(): creating new database"));
1475 
1476   RefPtr<CookiePersistentStorage> self = this;
1477   nsCOMPtr<nsIRunnable> runnable =
1478       NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [self] {
1479         // The database has been closed, and we're ready to rebuild. Open a
1480         // connection.
1481         OpenDBResult result = self->TryInitDB(true);
1482 
1483         nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction(
1484             "RebuildCorruptDB.TryInitDBComplete", [self, result] {
1485               nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1486               if (result != RESULT_OK) {
1487                 // We're done. Reset our DB connection and statements, and
1488                 // notify of closure.
1489                 COOKIE_LOGSTRING(
1490                     LogLevel::Warning,
1491                     ("RebuildCorruptDB(): TryInitDB() failed with result %u",
1492                      result));
1493                 self->CleanupCachedStatements();
1494                 self->CleanupDBConnection();
1495                 self->mCorruptFlag = CookiePersistentStorage::OK;
1496                 if (os) {
1497                   os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1498                 }
1499                 return;
1500               }
1501 
1502               // Notify observers that we're beginning the rebuild.
1503               if (os) {
1504                 os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1505               }
1506 
1507               self->InitDBConnInternal();
1508 
1509               // Enumerate the hash, and add cookies to the params array.
1510               mozIStorageAsyncStatement* stmt = self->mStmtInsert;
1511               nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1512               stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1513               for (auto iter = self->mHostTable.Iter(); !iter.Done();
1514                    iter.Next()) {
1515                 CookieEntry* entry = iter.Get();
1516 
1517                 const CookieEntry::ArrayType& cookies = entry->GetCookies();
1518                 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1519                   Cookie* cookie = cookies[i];
1520 
1521                   if (!cookie->IsSession()) {
1522                     BindCookieParameters(paramsArray, CookieKey(entry), cookie);
1523                   }
1524                 }
1525               }
1526 
1527               // Make sure we've got something to write. If we don't, we're
1528               // done.
1529               uint32_t length;
1530               paramsArray->GetLength(&length);
1531               if (length == 0) {
1532                 COOKIE_LOGSTRING(
1533                     LogLevel::Debug,
1534                     ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1535                 self->mCorruptFlag = CookiePersistentStorage::OK;
1536                 return;
1537               }
1538 
1539               self->MaybeStoreCookiesToDB(paramsArray);
1540             });
1541         NS_DispatchToMainThread(innerRunnable);
1542       });
1543   mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1544 }
1545 
HandleDBClosed()1546 void CookiePersistentStorage::HandleDBClosed() {
1547   COOKIE_LOGSTRING(LogLevel::Debug,
1548                    ("HandleDBClosed(): CookieStorage %p closed", this));
1549 
1550   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1551 
1552   switch (mCorruptFlag) {
1553     case CookiePersistentStorage::OK: {
1554       // Database is healthy. Notify of closure.
1555       if (os) {
1556         os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1557       }
1558       break;
1559     }
1560     case CookiePersistentStorage::CLOSING_FOR_REBUILD: {
1561       // Our close finished. Start the rebuild, and notify of db closure later.
1562       RebuildCorruptDB();
1563       break;
1564     }
1565     case CookiePersistentStorage::REBUILDING: {
1566       // We encountered an error during rebuild, closed the database, and now
1567       // here we are. We already have a 'cookies.sqlite.bak' from the original
1568       // dead database; we don't want to overwrite it, so let's move this one to
1569       // 'cookies.sqlite.bak-rebuild'.
1570       nsCOMPtr<nsIFile> backupFile;
1571       mCookieFile->Clone(getter_AddRefs(backupFile));
1572       nsresult rv = backupFile->MoveToNative(
1573           nullptr, NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
1574 
1575       COOKIE_LOGSTRING(LogLevel::Warning,
1576                        ("HandleDBClosed(): CookieStorage %p encountered error "
1577                         "rebuilding db; move to "
1578                         "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
1579                         this, static_cast<uint32_t>(rv)));
1580       if (os) {
1581         os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1582       }
1583       break;
1584     }
1585   }
1586 }
1587 
Read()1588 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::Read() {
1589   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
1590 
1591   // Read in the data synchronously.
1592   // see IDX_NAME, etc. for parameter indexes
1593   nsCOMPtr<mozIStorageStatement> stmt;
1594   nsresult rv =
1595       mSyncConn->CreateStatement(NS_LITERAL_CSTRING("SELECT "
1596                                                     "name, "
1597                                                     "value, "
1598                                                     "host, "
1599                                                     "path, "
1600                                                     "expiry, "
1601                                                     "lastAccessed, "
1602                                                     "creationTime, "
1603                                                     "isSecure, "
1604                                                     "isHttpOnly, "
1605                                                     "originAttributes, "
1606                                                     "sameSite, "
1607                                                     "rawSameSite "
1608                                                     "FROM moz_cookies"),
1609                                  getter_AddRefs(stmt));
1610 
1611   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1612 
1613   if (NS_WARN_IF(!mReadArray.IsEmpty())) {
1614     mReadArray.Clear();
1615   }
1616   mReadArray.SetCapacity(kMaxNumberOfCookies);
1617 
1618   nsCString baseDomain;
1619   nsCString name;
1620   nsCString value;
1621   nsCString host;
1622   nsCString path;
1623   bool hasResult;
1624   while (true) {
1625     rv = stmt->ExecuteStep(&hasResult);
1626     if (NS_WARN_IF(NS_FAILED(rv))) {
1627       mReadArray.Clear();
1628       return RESULT_RETRY;
1629     }
1630 
1631     if (!hasResult) {
1632       break;
1633     }
1634 
1635     stmt->GetUTF8String(IDX_HOST, host);
1636 
1637     rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
1638     if (NS_FAILED(rv)) {
1639       COOKIE_LOGSTRING(LogLevel::Debug,
1640                        ("Read(): Ignoring invalid host '%s'", host.get()));
1641       continue;
1642     }
1643 
1644     nsAutoCString suffix;
1645     OriginAttributes attrs;
1646     stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
1647     // If PopulateFromSuffix failed we just ignore the OA attributes
1648     // that we don't support
1649     Unused << attrs.PopulateFromSuffix(suffix);
1650 
1651     CookieKey key(baseDomain, attrs);
1652     CookieDomainTuple* tuple = mReadArray.AppendElement();
1653     tuple->key = std::move(key);
1654     tuple->originAttributes = attrs;
1655     tuple->cookie = GetCookieFromRow(stmt);
1656   }
1657 
1658   COOKIE_LOGSTRING(LogLevel::Debug,
1659                    ("Read(): %zu cookies read", mReadArray.Length()));
1660 
1661   return RESULT_OK;
1662 }
1663 
1664 // Extract data from a single result row and create an Cookie.
GetCookieFromRow(mozIStorageStatement * aRow)1665 UniquePtr<CookieStruct> CookiePersistentStorage::GetCookieFromRow(
1666     mozIStorageStatement* aRow) {
1667   nsCString name;
1668   nsCString value;
1669   nsCString host;
1670   nsCString path;
1671   DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
1672   MOZ_ASSERT(NS_SUCCEEDED(rv));
1673   rv = aRow->GetUTF8String(IDX_VALUE, value);
1674   MOZ_ASSERT(NS_SUCCEEDED(rv));
1675   rv = aRow->GetUTF8String(IDX_HOST, host);
1676   MOZ_ASSERT(NS_SUCCEEDED(rv));
1677   rv = aRow->GetUTF8String(IDX_PATH, path);
1678   MOZ_ASSERT(NS_SUCCEEDED(rv));
1679 
1680   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
1681   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
1682   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
1683   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
1684   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
1685   int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
1686   int32_t rawSameSite = aRow->AsInt32(IDX_RAW_SAME_SITE);
1687 
1688   // Create a new constCookie and assign the data.
1689   return MakeUnique<CookieStruct>(name, value, host, path, expiry, lastAccessed,
1690                                   creationTime, isHttpOnly, false, isSecure,
1691                                   sameSite, rawSameSite);
1692 }
1693 
EnsureReadComplete()1694 void CookiePersistentStorage::EnsureReadComplete() {
1695   MOZ_ASSERT(NS_IsMainThread());
1696 
1697   bool isAccumulated = false;
1698 
1699   if (!mInitialized) {
1700     TimeStamp startBlockTime = TimeStamp::Now();
1701     MonitorAutoLock lock(mMonitor);
1702 
1703     while (!mInitialized) {
1704       mMonitor.Wait();
1705     }
1706 
1707     Telemetry::AccumulateTimeDelta(
1708         Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, startBlockTime);
1709     Telemetry::Accumulate(
1710         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
1711     isAccumulated = true;
1712   } else if (!mEndInitDBConn.IsNull()) {
1713     // We didn't block main thread, and here comes the first cookie request.
1714     // Collect how close we're going to block main thread.
1715     Telemetry::Accumulate(
1716         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS,
1717         (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds());
1718     // Nullify the timestamp so wo don't accumulate this telemetry probe again.
1719     mEndInitDBConn = TimeStamp();
1720     isAccumulated = true;
1721   } else if (!mInitializedDBConn) {
1722     // A request comes while we finished cookie thread task and InitDBConn is
1723     // on the way from cookie thread to main thread. We're very close to block
1724     // main thread.
1725     Telemetry::Accumulate(
1726         Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
1727     isAccumulated = true;
1728   }
1729 
1730   if (!mInitializedDBConn) {
1731     InitDBConn();
1732     if (isAccumulated) {
1733       // Nullify the timestamp so wo don't accumulate this telemetry probe
1734       // again.
1735       mEndInitDBConn = TimeStamp();
1736     }
1737   }
1738 }
1739 
InitDBConn()1740 void CookiePersistentStorage::InitDBConn() {
1741   MOZ_ASSERT(NS_IsMainThread());
1742 
1743   // We should skip InitDBConn if we close profile during initializing
1744   // CookieStorages and then InitDBConn is called after we close the
1745   // CookieStorages.
1746   if (!mInitialized || mInitializedDBConn) {
1747     return;
1748   }
1749 
1750   for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
1751     CookieDomainTuple& tuple = mReadArray[i];
1752     MOZ_ASSERT(!tuple.cookie->isSession());
1753 
1754     RefPtr<Cookie> cookie =
1755         Cookie::Create(*tuple.cookie, tuple.originAttributes);
1756     AddCookieToList(tuple.key.mBaseDomain, tuple.key.mOriginAttributes, cookie);
1757   }
1758 
1759   if (NS_FAILED(InitDBConnInternal())) {
1760     COOKIE_LOGSTRING(LogLevel::Warning,
1761                      ("InitDBConn(): retrying InitDBConnInternal()"));
1762     CleanupCachedStatements();
1763     CleanupDBConnection();
1764     if (NS_FAILED(InitDBConnInternal())) {
1765       COOKIE_LOGSTRING(
1766           LogLevel::Warning,
1767           ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
1768 
1769       // Game over, clean the connections.
1770       CleanupCachedStatements();
1771       CleanupDBConnection();
1772     }
1773   }
1774   mInitializedDBConn = true;
1775 
1776   COOKIE_LOGSTRING(LogLevel::Debug,
1777                    ("InitDBConn(): mInitializedDBConn = true"));
1778   mEndInitDBConn = TimeStamp::Now();
1779 
1780   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1781   if (os) {
1782     os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
1783     mReadArray.Clear();
1784   }
1785 }
1786 
InitDBConnInternal()1787 nsresult CookiePersistentStorage::InitDBConnInternal() {
1788   MOZ_ASSERT(NS_IsMainThread());
1789 
1790   nsresult rv = mStorageService->OpenUnsharedDatabase(mCookieFile,
1791                                                       getter_AddRefs(mDBConn));
1792   NS_ENSURE_SUCCESS(rv, rv);
1793 
1794   // Set up our listeners.
1795   mInsertListener = new InsertCookieDBListener(this);
1796   mUpdateListener = new UpdateCookieDBListener(this);
1797   mRemoveListener = new RemoveCookieDBListener(this);
1798   mCloseListener = new CloseCookieDBListener(this);
1799 
1800   // Grow cookie db in 512KB increments
1801   mDBConn->SetGrowthIncrement(512 * 1024, EmptyCString());
1802 
1803   // make operations on the table asynchronous, for performance
1804   mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
1805 
1806   // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1807   // 16 pages (around 500KB).
1808   mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(MOZ_STORAGE_UNIQUIFY_QUERY_STR
1809                                                "PRAGMA journal_mode = WAL"));
1810   mDBConn->ExecuteSimpleSQL(
1811       NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = 16"));
1812 
1813   // cache frequently used statements (for insertion, deletion, and updating)
1814   rv = mDBConn->CreateAsyncStatement(
1815       NS_LITERAL_CSTRING("INSERT INTO moz_cookies ("
1816                          "originAttributes, "
1817                          "name, "
1818                          "value, "
1819                          "host, "
1820                          "path, "
1821                          "expiry, "
1822                          "lastAccessed, "
1823                          "creationTime, "
1824                          "isSecure, "
1825                          "isHttpOnly, "
1826                          "sameSite, "
1827                          "rawSameSite "
1828                          ") VALUES ("
1829                          ":originAttributes, "
1830                          ":name, "
1831                          ":value, "
1832                          ":host, "
1833                          ":path, "
1834                          ":expiry, "
1835                          ":lastAccessed, "
1836                          ":creationTime, "
1837                          ":isSecure, "
1838                          ":isHttpOnly, "
1839                          ":sameSite, "
1840                          ":rawSameSite "
1841                          ")"),
1842       getter_AddRefs(mStmtInsert));
1843   NS_ENSURE_SUCCESS(rv, rv);
1844 
1845   rv = mDBConn->CreateAsyncStatement(
1846       NS_LITERAL_CSTRING("DELETE FROM moz_cookies "
1847                          "WHERE name = :name AND host = :host AND path = :path "
1848                          "AND originAttributes = :originAttributes"),
1849       getter_AddRefs(mStmtDelete));
1850   NS_ENSURE_SUCCESS(rv, rv);
1851 
1852   rv = mDBConn->CreateAsyncStatement(
1853       NS_LITERAL_CSTRING("UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1854                          "WHERE name = :name AND host = :host AND path = :path "
1855                          "AND originAttributes = :originAttributes"),
1856       getter_AddRefs(mStmtUpdate));
1857   return rv;
1858 }
1859 
1860 // Sets the schema version and creates the moz_cookies table.
CreateTableWorker(const char * aName)1861 nsresult CookiePersistentStorage::CreateTableWorker(const char* aName) {
1862   // Create the table.
1863   // We default originAttributes to empty string: this is so if users revert to
1864   // an older Firefox version that doesn't know about this field, any cookies
1865   // set will still work once they upgrade back.
1866   nsAutoCString command("CREATE TABLE ");
1867   command.Append(aName);
1868   command.AppendLiteral(
1869       " ("
1870       "id INTEGER PRIMARY KEY, "
1871       "originAttributes TEXT NOT NULL DEFAULT '', "
1872       "name TEXT, "
1873       "value TEXT, "
1874       "host TEXT, "
1875       "path TEXT, "
1876       "expiry INTEGER, "
1877       "lastAccessed INTEGER, "
1878       "creationTime INTEGER, "
1879       "isSecure INTEGER, "
1880       "isHttpOnly INTEGER, "
1881       "inBrowserElement INTEGER DEFAULT 0, "
1882       "sameSite INTEGER DEFAULT 0, "
1883       "rawSameSite INTEGER DEFAULT 0, "
1884       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1885       ")");
1886   return mSyncConn->ExecuteSimpleSQL(command);
1887 }
1888 
1889 // Sets the schema version and creates the moz_cookies table.
CreateTable()1890 nsresult CookiePersistentStorage::CreateTable() {
1891   // Set the schema version, before creating the table.
1892   nsresult rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1893   if (NS_FAILED(rv)) {
1894     return rv;
1895   }
1896 
1897   rv = CreateTableWorker("moz_cookies");
1898   if (NS_FAILED(rv)) {
1899     return rv;
1900   }
1901 
1902   return NS_OK;
1903 }
1904 
1905 // Sets the schema version and creates the moz_cookies table.
CreateTableForSchemaVersion6()1906 nsresult CookiePersistentStorage::CreateTableForSchemaVersion6() {
1907   // Set the schema version, before creating the table.
1908   nsresult rv = mSyncConn->SetSchemaVersion(6);
1909   if (NS_FAILED(rv)) {
1910     return rv;
1911   }
1912 
1913   // Create the table.
1914   // We default originAttributes to empty string: this is so if users revert to
1915   // an older Firefox version that doesn't know about this field, any cookies
1916   // set will still work once they upgrade back.
1917   rv = mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1918       "CREATE TABLE moz_cookies ("
1919       "id INTEGER PRIMARY KEY, "
1920       "baseDomain TEXT, "
1921       "originAttributes TEXT NOT NULL DEFAULT '', "
1922       "name TEXT, "
1923       "value TEXT, "
1924       "host TEXT, "
1925       "path TEXT, "
1926       "expiry INTEGER, "
1927       "lastAccessed INTEGER, "
1928       "creationTime INTEGER, "
1929       "isSecure INTEGER, "
1930       "isHttpOnly INTEGER, "
1931       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1932       ")"));
1933   if (NS_FAILED(rv)) {
1934     return rv;
1935   }
1936 
1937   // Create an index on baseDomain.
1938   return mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1939       "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1940       "originAttributes)"));
1941 }
1942 
1943 // Sets the schema version and creates the moz_cookies table.
CreateTableForSchemaVersion5()1944 nsresult CookiePersistentStorage::CreateTableForSchemaVersion5() {
1945   // Set the schema version, before creating the table.
1946   nsresult rv = mSyncConn->SetSchemaVersion(5);
1947   if (NS_FAILED(rv)) {
1948     return rv;
1949   }
1950 
1951   // Create the table. We default appId/inBrowserElement to 0: this is so if
1952   // users revert to an older Firefox version that doesn't know about these
1953   // fields, any cookies set will still work once they upgrade back.
1954   rv = mSyncConn->ExecuteSimpleSQL(
1955       NS_LITERAL_CSTRING("CREATE TABLE moz_cookies ("
1956                          "id INTEGER PRIMARY KEY, "
1957                          "baseDomain TEXT, "
1958                          "appId INTEGER DEFAULT 0, "
1959                          "inBrowserElement INTEGER DEFAULT 0, "
1960                          "name TEXT, "
1961                          "value TEXT, "
1962                          "host TEXT, "
1963                          "path TEXT, "
1964                          "expiry INTEGER, "
1965                          "lastAccessed INTEGER, "
1966                          "creationTime INTEGER, "
1967                          "isSecure INTEGER, "
1968                          "isHttpOnly INTEGER, "
1969                          "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, "
1970                          "appId, inBrowserElement)"
1971                          ")"));
1972   if (NS_FAILED(rv)) {
1973     return rv;
1974   }
1975 
1976   // Create an index on baseDomain.
1977   return mSyncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1978       "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
1979       "appId, "
1980       "inBrowserElement)"));
1981 }
1982 
RunInTransaction(nsICookieTransactionCallback * aCallback)1983 nsresult CookiePersistentStorage::RunInTransaction(
1984     nsICookieTransactionCallback* aCallback) {
1985   if (NS_WARN_IF(!mDBConn)) {
1986     return NS_ERROR_NOT_AVAILABLE;
1987   }
1988 
1989   mozStorageTransaction transaction(mDBConn, true);
1990 
1991   if (NS_FAILED(aCallback->Callback())) {
1992     Unused << transaction.Rollback();
1993     return NS_ERROR_FAILURE;
1994   }
1995 
1996   return NS_OK;
1997 }
1998 
1999 // purges expired and old cookies in a batch operation.
PurgeCookies(int64_t aCurrentTimeInUsec,uint16_t aMaxNumberOfCookies,int64_t aCookiePurgeAge)2000 already_AddRefed<nsIArray> CookiePersistentStorage::PurgeCookies(
2001     int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
2002     int64_t aCookiePurgeAge) {
2003   // Create a params array to batch the removals. This is OK here because
2004   // all the removals are in order, and there are no interleaved additions.
2005   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
2006   if (mDBConn) {
2007     mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
2008   }
2009 
2010   RefPtr<CookiePersistentStorage> self = this;
2011 
2012   return PurgeCookiesWithCallbacks(
2013       aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge,
2014       [paramsArray, self](const CookieListIter& aIter) {
2015         self->PrepareCookieRemoval(aIter, paramsArray);
2016         self->RemoveCookieFromListInternal(aIter);
2017       },
2018       [paramsArray, self]() {
2019         if (paramsArray) {
2020           self->DeleteFromDB(paramsArray);
2021         }
2022       });
2023 }
2024 
2025 }  // namespace net
2026 }  // namespace mozilla
2027