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