Bug 1887774 convert from MediaEnginePrefs to AudioProcessing config in AudioInputProc...
[gecko.git] / netwerk / cookie / CookiePersistentStorage.cpp
blob57727ad4fc53164709f0c505b51c87da7952d98e
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/. */
6 #include "Cookie.h"
7 #include "CookieCommons.h"
8 #include "CookieLogging.h"
9 #include "CookiePersistentStorage.h"
11 #include "mozilla/FileUtils.h"
12 #include "mozilla/glean/GleanMetrics.h"
13 #include "mozilla/ScopeExit.h"
14 #include "mozilla/Telemetry.h"
15 #include "mozIStorageAsyncStatement.h"
16 #include "mozIStorageError.h"
17 #include "mozIStorageFunction.h"
18 #include "mozIStorageService.h"
19 #include "mozStorageHelper.h"
20 #include "nsAppDirectoryServiceDefs.h"
21 #include "nsICookieNotification.h"
22 #include "nsICookieService.h"
23 #include "nsIEffectiveTLDService.h"
24 #include "nsILineInputStream.h"
25 #include "nsIURIMutator.h"
26 #include "nsNetUtil.h"
27 #include "nsVariant.h"
28 #include "prprf.h"
30 // XXX_hack. See bug 178993.
31 // This is a hack to hide HttpOnly cookies from older browsers
32 #define HTTP_ONLY_PREFIX "#HttpOnly_"
34 constexpr auto COOKIES_SCHEMA_VERSION = 13;
36 // parameter indexes; see |Read|
37 constexpr auto IDX_NAME = 0;
38 constexpr auto IDX_VALUE = 1;
39 constexpr auto IDX_HOST = 2;
40 constexpr auto IDX_PATH = 3;
41 constexpr auto IDX_EXPIRY = 4;
42 constexpr auto IDX_LAST_ACCESSED = 5;
43 constexpr auto IDX_CREATION_TIME = 6;
44 constexpr auto IDX_SECURE = 7;
45 constexpr auto IDX_HTTPONLY = 8;
46 constexpr auto IDX_ORIGIN_ATTRIBUTES = 9;
47 constexpr auto IDX_SAME_SITE = 10;
48 constexpr auto IDX_RAW_SAME_SITE = 11;
49 constexpr auto IDX_SCHEME_MAP = 12;
50 constexpr auto IDX_PARTITIONED_ATTRIBUTE_SET = 13;
52 #define COOKIES_FILE "cookies.sqlite"
54 namespace mozilla {
55 namespace net {
57 namespace {
59 void BindCookieParameters(mozIStorageBindingParamsArray* aParamsArray,
60 const CookieKey& aKey, const Cookie* aCookie) {
61 NS_ASSERTION(aParamsArray,
62 "Null params array passed to BindCookieParameters!");
63 NS_ASSERTION(aCookie, "Null cookie passed to BindCookieParameters!");
65 // Use the asynchronous binding methods to ensure that we do not acquire the
66 // database lock.
67 nsCOMPtr<mozIStorageBindingParams> params;
68 DebugOnly<nsresult> rv =
69 aParamsArray->NewBindingParams(getter_AddRefs(params));
70 MOZ_ASSERT(NS_SUCCEEDED(rv));
72 nsAutoCString suffix;
73 aKey.mOriginAttributes.CreateSuffix(suffix);
74 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
75 MOZ_ASSERT(NS_SUCCEEDED(rv));
77 rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
78 MOZ_ASSERT(NS_SUCCEEDED(rv));
80 rv = params->BindUTF8StringByName("value"_ns, aCookie->Value());
81 MOZ_ASSERT(NS_SUCCEEDED(rv));
83 rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
84 MOZ_ASSERT(NS_SUCCEEDED(rv));
86 rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
87 MOZ_ASSERT(NS_SUCCEEDED(rv));
89 rv = params->BindInt64ByName("expiry"_ns, aCookie->Expiry());
90 MOZ_ASSERT(NS_SUCCEEDED(rv));
92 rv = params->BindInt64ByName("lastAccessed"_ns, aCookie->LastAccessed());
93 MOZ_ASSERT(NS_SUCCEEDED(rv));
95 rv = params->BindInt64ByName("creationTime"_ns, aCookie->CreationTime());
96 MOZ_ASSERT(NS_SUCCEEDED(rv));
98 rv = params->BindInt32ByName("isSecure"_ns, aCookie->IsSecure());
99 MOZ_ASSERT(NS_SUCCEEDED(rv));
101 rv = params->BindInt32ByName("isHttpOnly"_ns, aCookie->IsHttpOnly());
102 MOZ_ASSERT(NS_SUCCEEDED(rv));
104 rv = params->BindInt32ByName("sameSite"_ns, aCookie->SameSite());
105 MOZ_ASSERT(NS_SUCCEEDED(rv));
107 rv = params->BindInt32ByName("rawSameSite"_ns, aCookie->RawSameSite());
108 MOZ_ASSERT(NS_SUCCEEDED(rv));
110 rv = params->BindInt32ByName("schemeMap"_ns, aCookie->SchemeMap());
111 MOZ_ASSERT(NS_SUCCEEDED(rv));
113 rv = params->BindInt32ByName("isPartitionedAttributeSet"_ns,
114 aCookie->RawIsPartitioned());
115 MOZ_ASSERT(NS_SUCCEEDED(rv));
117 // Bind the params to the array.
118 rv = aParamsArray->AddParams(params);
119 MOZ_ASSERT(NS_SUCCEEDED(rv));
122 class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction {
123 ~ConvertAppIdToOriginAttrsSQLFunction() = default;
125 NS_DECL_ISUPPORTS
126 NS_DECL_MOZISTORAGEFUNCTION
129 NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
131 NS_IMETHODIMP
132 ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
133 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
134 nsresult rv;
135 OriginAttributes attrs;
136 nsAutoCString suffix;
137 attrs.CreateSuffix(suffix);
139 RefPtr<nsVariant> outVar(new nsVariant());
140 rv = outVar->SetAsAUTF8String(suffix);
141 NS_ENSURE_SUCCESS(rv, rv);
143 outVar.forget(aResult);
144 return NS_OK;
147 class SetAppIdFromOriginAttributesSQLFunction final
148 : public mozIStorageFunction {
149 ~SetAppIdFromOriginAttributesSQLFunction() = default;
151 NS_DECL_ISUPPORTS
152 NS_DECL_MOZISTORAGEFUNCTION
155 NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
157 NS_IMETHODIMP
158 SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
159 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
160 nsresult rv;
161 nsAutoCString suffix;
162 OriginAttributes attrs;
164 rv = aFunctionArguments->GetUTF8String(0, suffix);
165 NS_ENSURE_SUCCESS(rv, rv);
166 bool success = attrs.PopulateFromSuffix(suffix);
167 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
169 RefPtr<nsVariant> outVar(new nsVariant());
170 rv = outVar->SetAsInt32(0); // deprecated appId!
171 NS_ENSURE_SUCCESS(rv, rv);
173 outVar.forget(aResult);
174 return NS_OK;
177 class SetInBrowserFromOriginAttributesSQLFunction final
178 : public mozIStorageFunction {
179 ~SetInBrowserFromOriginAttributesSQLFunction() = default;
181 NS_DECL_ISUPPORTS
182 NS_DECL_MOZISTORAGEFUNCTION
185 NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
186 mozIStorageFunction);
188 NS_IMETHODIMP
189 SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
190 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
191 nsresult rv;
192 nsAutoCString suffix;
193 OriginAttributes attrs;
195 rv = aFunctionArguments->GetUTF8String(0, suffix);
196 NS_ENSURE_SUCCESS(rv, rv);
197 bool success = attrs.PopulateFromSuffix(suffix);
198 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
200 RefPtr<nsVariant> outVar(new nsVariant());
201 rv = outVar->SetAsInt32(false);
202 NS_ENSURE_SUCCESS(rv, rv);
204 outVar.forget(aResult);
205 return NS_OK;
208 /******************************************************************************
209 * DBListenerErrorHandler impl:
210 * Parent class for our async storage listeners that handles the logging of
211 * errors.
212 ******************************************************************************/
213 class DBListenerErrorHandler : public mozIStorageStatementCallback {
214 protected:
215 explicit DBListenerErrorHandler(CookiePersistentStorage* dbState)
216 : mStorage(dbState) {}
217 RefPtr<CookiePersistentStorage> mStorage;
218 virtual const char* GetOpType() = 0;
220 public:
221 NS_IMETHOD HandleError(mozIStorageError* aError) override {
222 if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
223 int32_t result = -1;
224 aError->GetResult(&result);
226 nsAutoCString message;
227 aError->GetMessage(message);
228 COOKIE_LOGSTRING(
229 LogLevel::Warning,
230 ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
231 "performing operation '%s' with message '%s'; rebuilding database.",
232 result, GetOpType(), message.get()));
235 // Rebuild the database.
236 mStorage->HandleCorruptDB();
238 return NS_OK;
242 /******************************************************************************
243 * InsertCookieDBListener impl:
244 * mozIStorageStatementCallback used to track asynchronous insertion operations.
245 ******************************************************************************/
246 class InsertCookieDBListener final : public DBListenerErrorHandler {
247 private:
248 const char* GetOpType() override { return "INSERT"; }
250 ~InsertCookieDBListener() = default;
252 public:
253 NS_DECL_ISUPPORTS
255 explicit InsertCookieDBListener(CookiePersistentStorage* dbState)
256 : DBListenerErrorHandler(dbState) {}
257 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
258 MOZ_ASSERT_UNREACHABLE(
259 "Unexpected call to "
260 "InsertCookieDBListener::HandleResult");
261 return NS_OK;
263 NS_IMETHOD HandleCompletion(uint16_t aReason) override {
264 // If we were rebuilding the db and we succeeded, make our mCorruptFlag say
265 // so.
266 if (mStorage->GetCorruptFlag() == CookiePersistentStorage::REBUILDING &&
267 aReason == mozIStorageStatementCallback::REASON_FINISHED) {
268 COOKIE_LOGSTRING(
269 LogLevel::Debug,
270 ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
271 mStorage->SetCorruptFlag(CookiePersistentStorage::OK);
274 // This notification is just for testing.
275 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
276 if (os) {
277 os->NotifyObservers(nullptr, "cookie-saved-on-disk", nullptr);
280 return NS_OK;
284 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
286 /******************************************************************************
287 * UpdateCookieDBListener impl:
288 * mozIStorageStatementCallback used to track asynchronous update operations.
289 ******************************************************************************/
290 class UpdateCookieDBListener final : public DBListenerErrorHandler {
291 private:
292 const char* GetOpType() override { return "UPDATE"; }
294 ~UpdateCookieDBListener() = default;
296 public:
297 NS_DECL_ISUPPORTS
299 explicit UpdateCookieDBListener(CookiePersistentStorage* dbState)
300 : DBListenerErrorHandler(dbState) {}
301 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
302 MOZ_ASSERT_UNREACHABLE(
303 "Unexpected call to "
304 "UpdateCookieDBListener::HandleResult");
305 return NS_OK;
307 NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
310 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
312 /******************************************************************************
313 * RemoveCookieDBListener impl:
314 * mozIStorageStatementCallback used to track asynchronous removal operations.
315 ******************************************************************************/
316 class RemoveCookieDBListener final : public DBListenerErrorHandler {
317 private:
318 const char* GetOpType() override { return "REMOVE"; }
320 ~RemoveCookieDBListener() = default;
322 public:
323 NS_DECL_ISUPPORTS
325 explicit RemoveCookieDBListener(CookiePersistentStorage* dbState)
326 : DBListenerErrorHandler(dbState) {}
327 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
328 MOZ_ASSERT_UNREACHABLE(
329 "Unexpected call to "
330 "RemoveCookieDBListener::HandleResult");
331 return NS_OK;
333 NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
336 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
338 /******************************************************************************
339 * CloseCookieDBListener imp:
340 * Static mozIStorageCompletionCallback used to notify when the database is
341 * successfully closed.
342 ******************************************************************************/
343 class CloseCookieDBListener final : public mozIStorageCompletionCallback {
344 ~CloseCookieDBListener() = default;
346 public:
347 explicit CloseCookieDBListener(CookiePersistentStorage* dbState)
348 : mStorage(dbState) {}
349 RefPtr<CookiePersistentStorage> mStorage;
350 NS_DECL_ISUPPORTS
352 NS_IMETHOD Complete(nsresult /*status*/, nsISupports* /*value*/) override {
353 mStorage->HandleDBClosed();
354 return NS_OK;
358 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
360 } // namespace
362 // static
363 already_AddRefed<CookiePersistentStorage> CookiePersistentStorage::Create() {
364 RefPtr<CookiePersistentStorage> storage = new CookiePersistentStorage();
365 storage->Init();
366 storage->Activate();
368 return storage.forget();
371 CookiePersistentStorage::CookiePersistentStorage()
372 : mMonitor("CookiePersistentStorage"),
373 mInitialized(false),
374 mCorruptFlag(OK) {}
376 void CookiePersistentStorage::NotifyChangedInternal(
377 nsICookieNotification* aNotification, bool aOldCookieIsSession) {
378 MOZ_ASSERT(aNotification);
379 // Notify for topic "session-cookie-changed" to update the copy of session
380 // cookies in session restore component.
382 nsICookieNotification::Action action = aNotification->GetAction();
384 // Filter out notifications for individual non-session cookies.
385 if (action == nsICookieNotification::COOKIE_CHANGED ||
386 action == nsICookieNotification::COOKIE_DELETED ||
387 action == nsICookieNotification::COOKIE_ADDED) {
388 nsCOMPtr<nsICookie> xpcCookie;
389 DebugOnly<nsresult> rv =
390 aNotification->GetCookie(getter_AddRefs(xpcCookie));
391 MOZ_ASSERT(NS_SUCCEEDED(rv) && xpcCookie);
392 const Cookie& cookie = xpcCookie->AsCookie();
393 if (!cookie.IsSession() && !aOldCookieIsSession) {
394 return;
398 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
399 if (os) {
400 os->NotifyObservers(aNotification, "session-cookie-changed", u"");
404 void CookiePersistentStorage::RemoveAllInternal() {
405 // clear the cookie file
406 if (mDBConn) {
407 nsCOMPtr<mozIStorageAsyncStatement> stmt;
408 nsresult rv = mDBConn->CreateAsyncStatement("DELETE FROM moz_cookies"_ns,
409 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();
424 void CookiePersistentStorage::HandleCorruptDB() {
425 COOKIE_LOGSTRING(LogLevel::Debug,
426 ("HandleCorruptDB(): CookieStorage %p has mCorruptFlag %u",
427 this, mCorruptFlag));
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;
436 CleanupCachedStatements();
437 mDBConn->AsyncClose(mCloseListener);
438 CleanupDBConnection();
439 break;
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;
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);
454 CleanupDBConnection();
455 break;
460 void CookiePersistentStorage::RemoveCookiesWithOriginAttributes(
461 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
462 mozStorageTransaction transaction(mDBConn, false);
464 // XXX Handle the error, bug 1696130.
465 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
467 CookieStorage::RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
469 DebugOnly<nsresult> rv = transaction.Commit();
470 MOZ_ASSERT(NS_SUCCEEDED(rv));
473 void CookiePersistentStorage::RemoveCookiesFromExactHost(
474 const nsACString& aHost, const nsACString& aBaseDomain,
475 const OriginAttributesPattern& aPattern) {
476 mozStorageTransaction transaction(mDBConn, false);
478 // XXX Handle the error, bug 1696130.
479 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
481 CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern);
483 DebugOnly<nsresult> rv = transaction.Commit();
484 MOZ_ASSERT(NS_SUCCEEDED(rv));
487 void CookiePersistentStorage::RemoveCookieFromDB(const Cookie& aCookie) {
488 // if it's a non-session cookie, remove it from the db
489 if (aCookie.IsSession() || !mDBConn) {
490 return;
493 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
494 mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
496 PrepareCookieRemoval(aCookie, paramsArray);
498 DebugOnly<nsresult> rv = mStmtDelete->BindParameters(paramsArray);
499 MOZ_ASSERT(NS_SUCCEEDED(rv));
501 nsCOMPtr<mozIStoragePendingStatement> handle;
502 rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
503 MOZ_ASSERT(NS_SUCCEEDED(rv));
506 void CookiePersistentStorage::PrepareCookieRemoval(
507 const Cookie& aCookie, mozIStorageBindingParamsArray* aParamsArray) {
508 // if it's a non-session cookie, remove it from the db
509 if (aCookie.IsSession() || !mDBConn) {
510 return;
513 nsCOMPtr<mozIStorageBindingParams> params;
514 aParamsArray->NewBindingParams(getter_AddRefs(params));
516 DebugOnly<nsresult> rv =
517 params->BindUTF8StringByName("name"_ns, aCookie.Name());
518 MOZ_ASSERT(NS_SUCCEEDED(rv));
520 rv = params->BindUTF8StringByName("host"_ns, aCookie.Host());
521 MOZ_ASSERT(NS_SUCCEEDED(rv));
523 rv = params->BindUTF8StringByName("path"_ns, aCookie.Path());
524 MOZ_ASSERT(NS_SUCCEEDED(rv));
526 nsAutoCString suffix;
527 aCookie.OriginAttributesRef().CreateSuffix(suffix);
528 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
529 MOZ_ASSERT(NS_SUCCEEDED(rv));
531 rv = aParamsArray->AddParams(params);
532 MOZ_ASSERT(NS_SUCCEEDED(rv));
535 // Null out the statements.
536 // This must be done before closing the connection.
537 void CookiePersistentStorage::CleanupCachedStatements() {
538 mStmtInsert = nullptr;
539 mStmtDelete = nullptr;
540 mStmtUpdate = nullptr;
543 // Null out the listeners, and the database connection itself. This
544 // will not null out the statements, cancel a pending read or
545 // asynchronously close the connection -- these must be done
546 // beforehand if necessary.
547 void CookiePersistentStorage::CleanupDBConnection() {
548 MOZ_ASSERT(!mStmtInsert, "mStmtInsert has been cleaned up");
549 MOZ_ASSERT(!mStmtDelete, "mStmtDelete has been cleaned up");
550 MOZ_ASSERT(!mStmtUpdate, "mStmtUpdate has been cleaned up");
552 // Null out the database connections. If 'mDBConn' has not been used for any
553 // asynchronous operations yet, this will synchronously close it; otherwise,
554 // it's expected that the caller has performed an AsyncClose prior.
555 mDBConn = nullptr;
557 // Manually null out our listeners. This is necessary because they hold a
558 // strong ref to the CookieStorage itself. They'll stay alive until whatever
559 // statements are still executing complete.
560 mInsertListener = nullptr;
561 mUpdateListener = nullptr;
562 mRemoveListener = nullptr;
563 mCloseListener = nullptr;
566 void CookiePersistentStorage::Close() {
567 if (mThread) {
568 mThread->Shutdown();
569 mThread = nullptr;
572 // Cleanup cached statements before we can close anything.
573 CleanupCachedStatements();
575 if (mDBConn) {
576 // Asynchronously close the connection. We will null it below.
577 mDBConn->AsyncClose(mCloseListener);
580 CleanupDBConnection();
582 mInitialized = false;
583 mInitializedDBConn = false;
586 void CookiePersistentStorage::StoreCookie(
587 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
588 Cookie* aCookie) {
589 // if it's a non-session cookie and hasn't just been read from the db, write
590 // it out.
591 if (aCookie->IsSession() || !mDBConn) {
592 return;
595 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
596 mStmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
598 CookieKey key(aBaseDomain, aOriginAttributes);
599 BindCookieParameters(paramsArray, key, aCookie);
601 MaybeStoreCookiesToDB(paramsArray);
604 void CookiePersistentStorage::MaybeStoreCookiesToDB(
605 mozIStorageBindingParamsArray* aParamsArray) {
606 if (!aParamsArray) {
607 return;
610 uint32_t length;
611 aParamsArray->GetLength(&length);
612 if (!length) {
613 return;
616 DebugOnly<nsresult> rv = mStmtInsert->BindParameters(aParamsArray);
617 MOZ_ASSERT(NS_SUCCEEDED(rv));
619 nsCOMPtr<mozIStoragePendingStatement> handle;
620 rv = mStmtInsert->ExecuteAsync(mInsertListener, getter_AddRefs(handle));
621 MOZ_ASSERT(NS_SUCCEEDED(rv));
624 void CookiePersistentStorage::StaleCookies(const nsTArray<Cookie*>& aCookieList,
625 int64_t aCurrentTimeInUsec) {
626 // Create an array of parameters to bind to our update statement. Batching
627 // is OK here since we're updating cookies with no interleaved operations.
628 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
629 mozIStorageAsyncStatement* stmt = mStmtUpdate;
630 if (mDBConn) {
631 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
634 int32_t count = aCookieList.Length();
635 for (int32_t i = 0; i < count; ++i) {
636 Cookie* cookie = aCookieList.ElementAt(i);
638 if (cookie->IsStale()) {
639 UpdateCookieInList(cookie, aCurrentTimeInUsec, paramsArray);
642 // Update the database now if necessary.
643 if (paramsArray) {
644 uint32_t length;
645 paramsArray->GetLength(&length);
646 if (length) {
647 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
648 MOZ_ASSERT(NS_SUCCEEDED(rv));
650 nsCOMPtr<mozIStoragePendingStatement> handle;
651 rv = stmt->ExecuteAsync(mUpdateListener, getter_AddRefs(handle));
652 MOZ_ASSERT(NS_SUCCEEDED(rv));
657 void CookiePersistentStorage::UpdateCookieInList(
658 Cookie* aCookie, int64_t aLastAccessed,
659 mozIStorageBindingParamsArray* aParamsArray) {
660 MOZ_ASSERT(aCookie);
662 // udpate the lastAccessed timestamp
663 aCookie->SetLastAccessed(aLastAccessed);
665 // if it's a non-session cookie, update it in the db too
666 if (!aCookie->IsSession() && aParamsArray) {
667 // Create our params holder.
668 nsCOMPtr<mozIStorageBindingParams> params;
669 aParamsArray->NewBindingParams(getter_AddRefs(params));
671 // Bind our parameters.
672 DebugOnly<nsresult> rv =
673 params->BindInt64ByName("lastAccessed"_ns, aLastAccessed);
674 MOZ_ASSERT(NS_SUCCEEDED(rv));
676 rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
677 MOZ_ASSERT(NS_SUCCEEDED(rv));
679 rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
680 MOZ_ASSERT(NS_SUCCEEDED(rv));
682 rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
683 MOZ_ASSERT(NS_SUCCEEDED(rv));
685 nsAutoCString suffix;
686 aCookie->OriginAttributesRef().CreateSuffix(suffix);
687 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
688 MOZ_ASSERT(NS_SUCCEEDED(rv));
690 // Add our bound parameters to the array.
691 rv = aParamsArray->AddParams(params);
692 MOZ_ASSERT(NS_SUCCEEDED(rv));
696 void CookiePersistentStorage::DeleteFromDB(
697 mozIStorageBindingParamsArray* aParamsArray) {
698 uint32_t length;
699 aParamsArray->GetLength(&length);
700 if (length) {
701 DebugOnly<nsresult> rv = mStmtDelete->BindParameters(aParamsArray);
702 MOZ_ASSERT(NS_SUCCEEDED(rv));
704 nsCOMPtr<mozIStoragePendingStatement> handle;
705 rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
706 MOZ_ASSERT(NS_SUCCEEDED(rv));
710 void CookiePersistentStorage::Activate() {
711 MOZ_ASSERT(!mThread, "already have a cookie thread");
713 mStorageService = do_GetService("@mozilla.org/storage/service;1");
714 MOZ_ASSERT(mStorageService);
716 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
717 MOZ_ASSERT(mTLDService);
719 // Get our cookie file.
720 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
721 getter_AddRefs(mCookieFile));
722 if (NS_FAILED(rv)) {
723 // We've already set up our CookieStorages appropriately; nothing more to
724 // do.
725 COOKIE_LOGSTRING(LogLevel::Warning,
726 ("InitCookieStorages(): couldn't get cookie file"));
728 mInitializedDBConn = true;
729 mInitialized = true;
730 return;
733 mCookieFile->AppendNative(nsLiteralCString(COOKIES_FILE));
735 NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread)));
737 RefPtr<CookiePersistentStorage> self = this;
738 nsCOMPtr<nsIRunnable> runnable =
739 NS_NewRunnableFunction("CookiePersistentStorage::Activate", [self] {
740 MonitorAutoLock lock(self->mMonitor);
742 // Attempt to open and read the database. If TryInitDB() returns
743 // RESULT_RETRY, do so.
744 OpenDBResult result = self->TryInitDB(false);
745 if (result == RESULT_RETRY) {
746 // Database may be corrupt. Synchronously close the connection, clean
747 // up the default CookieStorage, and try again.
748 COOKIE_LOGSTRING(LogLevel::Warning,
749 ("InitCookieStorages(): retrying TryInitDB()"));
750 self->CleanupCachedStatements();
751 self->CleanupDBConnection();
752 result = self->TryInitDB(true);
753 if (result == RESULT_RETRY) {
754 // We're done. Change the code to failure so we clean up below.
755 result = RESULT_FAILURE;
759 if (result == RESULT_FAILURE) {
760 COOKIE_LOGSTRING(
761 LogLevel::Warning,
762 ("InitCookieStorages(): TryInitDB() failed, closing connection"));
764 // Connection failure is unrecoverable. Clean up our connection. We
765 // can run fine without persistent storage -- e.g. if there's no
766 // profile.
767 self->CleanupCachedStatements();
768 self->CleanupDBConnection();
770 // No need to initialize mDBConn
771 self->mInitializedDBConn = true;
774 self->mInitialized = true;
776 NS_DispatchToMainThread(
777 NS_NewRunnableFunction("CookiePersistentStorage::InitDBConn",
778 [self] { self->InitDBConn(); }));
779 self->mMonitor.Notify();
782 mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
785 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
786 * move the existing database file out of the way and create a new one.
788 * @returns RESULT_OK if opening or creating the database succeeded;
789 * RESULT_RETRY if the database cannot be opened, is corrupt, or some
790 * other failure occurred that might be resolved by recreating the
791 * database; or RESULT_FAILED if there was an unrecoverable error and
792 * we must run without a database.
794 * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
795 * cleanup of the default CookieStorage.
797 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::TryInitDB(
798 bool aRecreateDB) {
799 NS_ASSERTION(!mDBConn, "nonnull mDBConn");
800 NS_ASSERTION(!mStmtInsert, "nonnull mStmtInsert");
801 NS_ASSERTION(!mInsertListener, "nonnull mInsertListener");
802 NS_ASSERTION(!mSyncConn, "nonnull mSyncConn");
803 NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
805 // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
806 // want to delete it outright, since it may be useful for debugging purposes,
807 // so we move it out of the way.
808 nsresult rv;
809 if (aRecreateDB) {
810 nsCOMPtr<nsIFile> backupFile;
811 mCookieFile->Clone(getter_AddRefs(backupFile));
812 rv = backupFile->MoveToNative(nullptr,
813 nsLiteralCString(COOKIES_FILE ".bak"));
814 NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
817 // This block provides scope for the Telemetry AutoTimer
819 Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
820 telemetry;
821 ReadAheadFile(mCookieFile);
823 // open a connection to the cookie database, and only cache our connection
824 // and statements upon success. The connection is opened unshared to
825 // eliminate cache contention between the main and background threads.
826 rv = mStorageService->OpenUnsharedDatabase(
827 mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
828 getter_AddRefs(mSyncConn));
829 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
832 auto guard = MakeScopeExit([&] { mSyncConn = nullptr; });
834 bool tableExists = false;
835 mSyncConn->TableExists("moz_cookies"_ns, &tableExists);
836 if (!tableExists) {
837 rv = CreateTable();
838 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
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);
846 // Start a transaction for the whole migration block.
847 mozStorageTransaction transaction(mSyncConn, true);
849 // XXX Handle the error, bug 1696130.
850 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
852 switch (dbSchemaVersion) {
853 // Upgrading.
854 // Every time you increment the database schema, you need to implement
855 // the upgrading code from the previous version to the new one. If
856 // migration fails for any reason, it's a bug -- so we return RESULT_RETRY
857 // such that the original database will be saved, in the hopes that we
858 // might one day see it and fix it.
859 case 1: {
860 // Add the lastAccessed column to the table.
861 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
862 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
863 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
865 // Fall through to the next upgrade.
866 [[fallthrough]];
868 case 2: {
869 // Add the baseDomain column and index to the table.
870 rv = mSyncConn->ExecuteSimpleSQL(
871 "ALTER TABLE moz_cookies ADD baseDomain TEXT"_ns);
872 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
874 // Compute the baseDomains for the table. This must be done eagerly
875 // otherwise we won't be able to synchronously read in individual
876 // domains on demand.
877 const int64_t SCHEMA2_IDX_ID = 0;
878 const int64_t SCHEMA2_IDX_HOST = 1;
879 nsCOMPtr<mozIStorageStatement> select;
880 rv = mSyncConn->CreateStatement("SELECT id, host FROM moz_cookies"_ns,
881 getter_AddRefs(select));
882 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
884 nsCOMPtr<mozIStorageStatement> update;
885 rv = mSyncConn->CreateStatement(
886 nsLiteralCString("UPDATE moz_cookies SET baseDomain = "
887 ":baseDomain WHERE id = :id"),
888 getter_AddRefs(update));
889 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
891 nsCString baseDomain;
892 nsCString host;
893 bool hasResult;
894 while (true) {
895 rv = select->ExecuteStep(&hasResult);
896 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
898 if (!hasResult) {
899 break;
902 int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
903 select->GetUTF8String(SCHEMA2_IDX_HOST, host);
905 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host,
906 baseDomain);
907 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
909 mozStorageStatementScoper scoper(update);
911 rv = update->BindUTF8StringByName("baseDomain"_ns, baseDomain);
912 MOZ_ASSERT(NS_SUCCEEDED(rv));
913 rv = update->BindInt64ByName("id"_ns, id);
914 MOZ_ASSERT(NS_SUCCEEDED(rv));
916 rv = update->ExecuteStep(&hasResult);
917 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
920 // Create an index on baseDomain.
921 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
922 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
923 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
925 // Fall through to the next upgrade.
926 [[fallthrough]];
928 case 3: {
929 // Add the creationTime column to the table, and create a unique index
930 // on (name, host, path). Before we do this, we have to purge the table
931 // of expired cookies such that we know that the (name, host, path)
932 // index is truly unique -- otherwise we can't create the index. Note
933 // that we can't just execute a statement to delete all rows where the
934 // expiry column is in the past -- doing so would rely on the clock
935 // (both now and when previous cookies were set) being monotonic.
937 // Select the whole table, and order by the fields we're interested in.
938 // This means we can simply do a linear traversal of the results and
939 // check for duplicates as we go.
940 const int64_t SCHEMA3_IDX_ID = 0;
941 const int64_t SCHEMA3_IDX_NAME = 1;
942 const int64_t SCHEMA3_IDX_HOST = 2;
943 const int64_t SCHEMA3_IDX_PATH = 3;
944 nsCOMPtr<mozIStorageStatement> select;
945 rv = mSyncConn->CreateStatement(
946 nsLiteralCString(
947 "SELECT id, name, host, path FROM moz_cookies "
948 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
949 getter_AddRefs(select));
950 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
952 nsCOMPtr<mozIStorageStatement> deleteExpired;
953 rv = mSyncConn->CreateStatement(
954 "DELETE FROM moz_cookies WHERE id = :id"_ns,
955 getter_AddRefs(deleteExpired));
956 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
958 // Read the first row.
959 bool hasResult;
960 rv = select->ExecuteStep(&hasResult);
961 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
963 if (hasResult) {
964 nsCString name1;
965 nsCString host1;
966 nsCString path1;
967 int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
968 select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
969 select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
970 select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
972 nsCString name2;
973 nsCString host2;
974 nsCString path2;
975 while (true) {
976 // Read the second row.
977 rv = select->ExecuteStep(&hasResult);
978 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
980 if (!hasResult) {
981 break;
984 int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
985 select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
986 select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
987 select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
989 // If the two rows match in (name, host, path), we know the earlier
990 // row has an earlier expiry time. Delete it.
991 if (name1 == name2 && host1 == host2 && path1 == path2) {
992 mozStorageStatementScoper scoper(deleteExpired);
994 rv = deleteExpired->BindInt64ByName("id"_ns, id1);
995 MOZ_ASSERT(NS_SUCCEEDED(rv));
997 rv = deleteExpired->ExecuteStep(&hasResult);
998 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1001 // Make the second row the first for the next iteration.
1002 name1 = name2;
1003 host1 = host2;
1004 path1 = path2;
1005 id1 = id2;
1009 // Add the creationTime column to the table.
1010 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1011 "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
1012 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1014 // Copy the id of each row into the new creationTime column.
1015 rv = mSyncConn->ExecuteSimpleSQL(
1016 nsLiteralCString("UPDATE moz_cookies SET creationTime = "
1017 "(SELECT id WHERE id = moz_cookies.id)"));
1018 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1020 // Create a unique index on (name, host, path) to allow fast lookup.
1021 rv = mSyncConn->ExecuteSimpleSQL(
1022 nsLiteralCString("CREATE UNIQUE INDEX moz_uniqueid "
1023 "ON moz_cookies (name, host, path)"));
1024 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1026 // Fall through to the next upgrade.
1027 [[fallthrough]];
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.
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.
1041 // Rename existing table
1042 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1043 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1044 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1046 // Drop existing index (CreateTable will create new one for new table)
1047 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
1048 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1050 // Create new table (with new fields and new unique constraint)
1051 rv = CreateTableForSchemaVersion5();
1052 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1054 // Copy data from old table, using appId/inBrowser=0 for existing rows
1055 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1056 "INSERT INTO moz_cookies "
1057 "(baseDomain, appId, inBrowserElement, name, value, host, path, "
1058 "expiry,"
1059 " lastAccessed, creationTime, isSecure, isHttpOnly) "
1060 "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
1061 " lastAccessed, creationTime, isSecure, isHttpOnly "
1062 "FROM moz_cookies_old"));
1063 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1065 // Drop old table
1066 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
1067 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1069 COOKIE_LOGSTRING(LogLevel::Debug,
1070 ("Upgraded database to schema version 5"));
1072 // Fall through to the next upgrade.
1073 [[fallthrough]];
1075 case 5: {
1076 // Change in the version: Replace the columns |appId| and
1077 // |inBrowserElement| by a single column |originAttributes|.
1079 // Why we made this change: FxOS new security model (NSec) encapsulates
1080 // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to
1081 // make it easier to modify the contents of this structure in the
1082 // future.
1084 // We do the migration in several steps:
1085 // 1. Rename the old table.
1086 // 2. Create a new table.
1087 // 3. Copy data from the old table to the new table; convert appId and
1088 // inBrowserElement to originAttributes in the meantime.
1090 // Rename existing table.
1091 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1092 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1093 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1095 // Drop existing index (CreateTable will create new one for new table).
1096 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
1097 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1099 // Create new table with new fields and new unique constraint.
1100 rv = CreateTableForSchemaVersion6();
1101 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1103 // Copy data from old table without the two deprecated columns appId and
1104 // inBrowserElement.
1105 nsCOMPtr<mozIStorageFunction> convertToOriginAttrs(
1106 new ConvertAppIdToOriginAttrsSQLFunction());
1107 NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
1109 constexpr auto convertToOriginAttrsName =
1110 "CONVERT_TO_ORIGIN_ATTRIBUTES"_ns;
1112 rv = mSyncConn->CreateFunction(convertToOriginAttrsName, 2,
1113 convertToOriginAttrs);
1114 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1116 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1117 "INSERT INTO moz_cookies "
1118 "(baseDomain, originAttributes, name, value, host, path, expiry,"
1119 " lastAccessed, creationTime, isSecure, isHttpOnly) "
1120 "SELECT baseDomain, "
1121 " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
1122 " name, value, host, path, expiry, lastAccessed, creationTime, "
1123 " isSecure, isHttpOnly "
1124 "FROM moz_cookies_old"));
1125 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1127 rv = mSyncConn->RemoveFunction(convertToOriginAttrsName);
1128 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1130 // Drop old table
1131 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
1132 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1134 COOKIE_LOGSTRING(LogLevel::Debug,
1135 ("Upgraded database to schema version 6"));
1137 [[fallthrough]];
1139 case 6: {
1140 // We made a mistake in schema version 6. We cannot remove expected
1141 // columns of any version (checked in the default case) from cookie
1142 // database, because doing this would destroy the possibility of
1143 // downgrading database.
1145 // This version simply restores appId and inBrowserElement columns in
1146 // order to fix downgrading issue even though these two columns are no
1147 // longer used in the latest schema.
1148 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1149 "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
1150 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1152 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1153 "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
1154 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1156 // Compute and populate the values of appId and inBrwoserElement from
1157 // originAttributes.
1158 nsCOMPtr<mozIStorageFunction> setAppId(
1159 new SetAppIdFromOriginAttributesSQLFunction());
1160 NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
1162 constexpr auto setAppIdName = "SET_APP_ID"_ns;
1164 rv = mSyncConn->CreateFunction(setAppIdName, 1, setAppId);
1165 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1167 nsCOMPtr<mozIStorageFunction> setInBrowser(
1168 new SetInBrowserFromOriginAttributesSQLFunction());
1169 NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
1171 constexpr auto setInBrowserName = "SET_IN_BROWSER"_ns;
1173 rv = mSyncConn->CreateFunction(setInBrowserName, 1, setInBrowser);
1174 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1176 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1177 "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
1178 "inBrowserElement = SET_IN_BROWSER(originAttributes);"));
1179 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1181 rv = mSyncConn->RemoveFunction(setAppIdName);
1182 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1184 rv = mSyncConn->RemoveFunction(setInBrowserName);
1185 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1187 COOKIE_LOGSTRING(LogLevel::Debug,
1188 ("Upgraded database to schema version 7"));
1190 [[fallthrough]];
1192 case 7: {
1193 // Remove the appId field from moz_cookies.
1195 // Unfortunately sqlite doesn't support dropping columns using ALTER
1196 // TABLE, so we need to go through the procedure documented in
1197 // https://www.sqlite.org/lang_altertable.html.
1199 // Drop existing index
1200 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
1201 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1203 // Create a new_moz_cookies table without the appId field.
1204 rv = mSyncConn->ExecuteSimpleSQL(
1205 nsLiteralCString("CREATE TABLE new_moz_cookies("
1206 "id INTEGER PRIMARY KEY, "
1207 "baseDomain TEXT, "
1208 "originAttributes TEXT NOT NULL DEFAULT '', "
1209 "name TEXT, "
1210 "value TEXT, "
1211 "host TEXT, "
1212 "path TEXT, "
1213 "expiry INTEGER, "
1214 "lastAccessed INTEGER, "
1215 "creationTime INTEGER, "
1216 "isSecure INTEGER, "
1217 "isHttpOnly INTEGER, "
1218 "inBrowserElement INTEGER DEFAULT 0, "
1219 "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
1220 "path, originAttributes)"
1221 ")"));
1222 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1224 // Move the data over.
1225 rv = mSyncConn->ExecuteSimpleSQL(
1226 nsLiteralCString("INSERT INTO new_moz_cookies ("
1227 "id, "
1228 "baseDomain, "
1229 "originAttributes, "
1230 "name, "
1231 "value, "
1232 "host, "
1233 "path, "
1234 "expiry, "
1235 "lastAccessed, "
1236 "creationTime, "
1237 "isSecure, "
1238 "isHttpOnly, "
1239 "inBrowserElement "
1240 ") SELECT "
1241 "id, "
1242 "baseDomain, "
1243 "originAttributes, "
1244 "name, "
1245 "value, "
1246 "host, "
1247 "path, "
1248 "expiry, "
1249 "lastAccessed, "
1250 "creationTime, "
1251 "isSecure, "
1252 "isHttpOnly, "
1253 "inBrowserElement "
1254 "FROM moz_cookies;"));
1255 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1257 // Drop the old table
1258 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies;"_ns);
1259 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1261 // Rename new_moz_cookies to moz_cookies.
1262 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1263 "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
1264 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1266 // Recreate our index.
1267 rv = mSyncConn->ExecuteSimpleSQL(
1268 nsLiteralCString("CREATE INDEX moz_basedomain ON moz_cookies "
1269 "(baseDomain, originAttributes)"));
1270 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1272 COOKIE_LOGSTRING(LogLevel::Debug,
1273 ("Upgraded database to schema version 8"));
1275 [[fallthrough]];
1277 case 8: {
1278 // Add the sameSite column to the table.
1279 rv = mSyncConn->ExecuteSimpleSQL(
1280 "ALTER TABLE moz_cookies ADD sameSite INTEGER"_ns);
1281 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1283 COOKIE_LOGSTRING(LogLevel::Debug,
1284 ("Upgraded database to schema version 9"));
1286 [[fallthrough]];
1288 case 9: {
1289 // Add the rawSameSite column to the table.
1290 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1291 "ALTER TABLE moz_cookies ADD rawSameSite INTEGER"));
1292 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1294 // Copy the current sameSite value into rawSameSite.
1295 rv = mSyncConn->ExecuteSimpleSQL(
1296 "UPDATE moz_cookies SET rawSameSite = sameSite"_ns);
1297 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1299 COOKIE_LOGSTRING(LogLevel::Debug,
1300 ("Upgraded database to schema version 10"));
1302 [[fallthrough]];
1304 case 10: {
1305 // Rename existing table
1306 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1307 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
1308 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1310 // Create a new moz_cookies table without the baseDomain field.
1311 rv = mSyncConn->ExecuteSimpleSQL(
1312 nsLiteralCString("CREATE TABLE moz_cookies("
1313 "id INTEGER PRIMARY KEY, "
1314 "originAttributes TEXT NOT NULL DEFAULT '', "
1315 "name TEXT, "
1316 "value TEXT, "
1317 "host TEXT, "
1318 "path TEXT, "
1319 "expiry INTEGER, "
1320 "lastAccessed INTEGER, "
1321 "creationTime INTEGER, "
1322 "isSecure INTEGER, "
1323 "isHttpOnly INTEGER, "
1324 "inBrowserElement INTEGER DEFAULT 0, "
1325 "sameSite INTEGER DEFAULT 0, "
1326 "rawSameSite INTEGER DEFAULT 0, "
1327 "CONSTRAINT moz_uniqueid UNIQUE (name, host, "
1328 "path, originAttributes)"
1329 ")"));
1330 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1332 // Move the data over.
1333 rv = mSyncConn->ExecuteSimpleSQL(
1334 nsLiteralCString("INSERT INTO moz_cookies ("
1335 "id, "
1336 "originAttributes, "
1337 "name, "
1338 "value, "
1339 "host, "
1340 "path, "
1341 "expiry, "
1342 "lastAccessed, "
1343 "creationTime, "
1344 "isSecure, "
1345 "isHttpOnly, "
1346 "inBrowserElement, "
1347 "sameSite, "
1348 "rawSameSite "
1349 ") SELECT "
1350 "id, "
1351 "originAttributes, "
1352 "name, "
1353 "value, "
1354 "host, "
1355 "path, "
1356 "expiry, "
1357 "lastAccessed, "
1358 "creationTime, "
1359 "isSecure, "
1360 "isHttpOnly, "
1361 "inBrowserElement, "
1362 "sameSite, "
1363 "rawSameSite "
1364 "FROM moz_cookies_old "
1365 "WHERE baseDomain NOTNULL;"));
1366 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1368 // Drop the old table
1369 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old;"_ns);
1370 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1372 // Drop the moz_basedomain index from the database (if it hasn't been
1373 // removed already by removing the table).
1374 rv = mSyncConn->ExecuteSimpleSQL(
1375 "DROP INDEX IF EXISTS moz_basedomain;"_ns);
1376 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1378 COOKIE_LOGSTRING(LogLevel::Debug,
1379 ("Upgraded database to schema version 11"));
1381 [[fallthrough]];
1383 case 11: {
1384 // Add the schemeMap column to the table.
1385 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1386 "ALTER TABLE moz_cookies ADD schemeMap INTEGER DEFAULT 0;"));
1387 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1389 COOKIE_LOGSTRING(LogLevel::Debug,
1390 ("Upgraded database to schema version 12"));
1392 // No more upgrades. Update the schema version.
1393 rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1394 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1396 [[fallthrough]];
1398 case 12: {
1399 // Add the isPartitionedAttributeSet column to the table.
1400 rv = mSyncConn->ExecuteSimpleSQL(
1401 nsLiteralCString("ALTER TABLE moz_cookies ADD "
1402 "isPartitionedAttributeSet INTEGER DEFAULT 0;"));
1403 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1405 COOKIE_LOGSTRING(LogLevel::Debug,
1406 ("Upgraded database to schema version 13"));
1408 // No more upgrades. Update the schema version.
1409 rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1410 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1412 [[fallthrough]];
1415 case COOKIES_SCHEMA_VERSION:
1416 break;
1418 case 0: {
1419 NS_WARNING("couldn't get schema version!");
1421 // the table may be usable; someone might've just clobbered the schema
1422 // version. we can treat this case like a downgrade using the codepath
1423 // below, by verifying the columns we care about are all there. for now,
1424 // re-set the schema version in the db, in case the checks succeed (if
1425 // they don't, we're dropping the table anyway).
1426 rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1427 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1429 // fall through to downgrade check
1430 [[fallthrough]];
1432 // downgrading.
1433 // if columns have been added to the table, we can still use the ones we
1434 // understand safely. if columns have been deleted or altered, just
1435 // blow away the table and start from scratch! if you change the way
1436 // a column is interpreted, make sure you also change its name so this
1437 // check will catch it.
1438 default: {
1439 // check if all the expected columns exist
1440 nsCOMPtr<mozIStorageStatement> stmt;
1441 rv = mSyncConn->CreateStatement(
1442 nsLiteralCString("SELECT "
1443 "id, "
1444 "originAttributes, "
1445 "name, "
1446 "value, "
1447 "host, "
1448 "path, "
1449 "expiry, "
1450 "lastAccessed, "
1451 "creationTime, "
1452 "isSecure, "
1453 "isHttpOnly, "
1454 "sameSite, "
1455 "rawSameSite, "
1456 "schemeMap, "
1457 "isPartitionedAttributeSet "
1458 "FROM moz_cookies"),
1459 getter_AddRefs(stmt));
1460 if (NS_SUCCEEDED(rv)) {
1461 break;
1464 // our columns aren't there - drop the table!
1465 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies"_ns);
1466 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1468 rv = CreateTable();
1469 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1470 } break;
1474 // if we deleted a corrupt db, don't attempt to import - return now
1475 if (aRecreateDB) {
1476 return RESULT_OK;
1479 // check whether to import or just read in the db
1480 if (tableExists) {
1481 return Read();
1484 return RESULT_OK;
1487 void CookiePersistentStorage::RebuildCorruptDB() {
1488 NS_ASSERTION(!mDBConn, "shouldn't have an open db connection");
1489 NS_ASSERTION(mCorruptFlag == CookiePersistentStorage::CLOSING_FOR_REBUILD,
1490 "should be in CLOSING_FOR_REBUILD state");
1492 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1494 mCorruptFlag = CookiePersistentStorage::REBUILDING;
1496 COOKIE_LOGSTRING(LogLevel::Debug,
1497 ("RebuildCorruptDB(): creating new database"));
1499 RefPtr<CookiePersistentStorage> self = this;
1500 nsCOMPtr<nsIRunnable> runnable =
1501 NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [self] {
1502 // The database has been closed, and we're ready to rebuild. Open a
1503 // connection.
1504 OpenDBResult result = self->TryInitDB(true);
1506 nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction(
1507 "RebuildCorruptDB.TryInitDBComplete", [self, result] {
1508 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1509 if (result != RESULT_OK) {
1510 // We're done. Reset our DB connection and statements, and
1511 // notify of closure.
1512 COOKIE_LOGSTRING(
1513 LogLevel::Warning,
1514 ("RebuildCorruptDB(): TryInitDB() failed with result %u",
1515 result));
1516 self->CleanupCachedStatements();
1517 self->CleanupDBConnection();
1518 self->mCorruptFlag = CookiePersistentStorage::OK;
1519 if (os) {
1520 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1522 return;
1525 // Notify observers that we're beginning the rebuild.
1526 if (os) {
1527 os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
1530 self->InitDBConnInternal();
1532 // Enumerate the hash, and add cookies to the params array.
1533 mozIStorageAsyncStatement* stmt = self->mStmtInsert;
1534 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
1535 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
1536 for (auto iter = self->mHostTable.Iter(); !iter.Done();
1537 iter.Next()) {
1538 CookieEntry* entry = iter.Get();
1540 const CookieEntry::ArrayType& cookies = entry->GetCookies();
1541 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
1542 Cookie* cookie = cookies[i];
1544 if (!cookie->IsSession()) {
1545 BindCookieParameters(paramsArray, CookieKey(entry), cookie);
1550 // Make sure we've got something to write. If we don't, we're
1551 // done.
1552 uint32_t length;
1553 paramsArray->GetLength(&length);
1554 if (length == 0) {
1555 COOKIE_LOGSTRING(
1556 LogLevel::Debug,
1557 ("RebuildCorruptDB(): nothing to write, rebuild complete"));
1558 self->mCorruptFlag = CookiePersistentStorage::OK;
1559 return;
1562 self->MaybeStoreCookiesToDB(paramsArray);
1564 NS_DispatchToMainThread(innerRunnable);
1566 mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1569 void CookiePersistentStorage::HandleDBClosed() {
1570 COOKIE_LOGSTRING(LogLevel::Debug,
1571 ("HandleDBClosed(): CookieStorage %p closed", this));
1573 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1575 switch (mCorruptFlag) {
1576 case CookiePersistentStorage::OK: {
1577 // Database is healthy. Notify of closure.
1578 if (os) {
1579 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1581 break;
1583 case CookiePersistentStorage::CLOSING_FOR_REBUILD: {
1584 // Our close finished. Start the rebuild, and notify of db closure later.
1585 RebuildCorruptDB();
1586 break;
1588 case CookiePersistentStorage::REBUILDING: {
1589 // We encountered an error during rebuild, closed the database, and now
1590 // here we are. We already have a 'cookies.sqlite.bak' from the original
1591 // dead database; we don't want to overwrite it, so let's move this one to
1592 // 'cookies.sqlite.bak-rebuild'.
1593 nsCOMPtr<nsIFile> backupFile;
1594 mCookieFile->Clone(getter_AddRefs(backupFile));
1595 nsresult rv = backupFile->MoveToNative(
1596 nullptr, nsLiteralCString(COOKIES_FILE ".bak-rebuild"));
1598 COOKIE_LOGSTRING(LogLevel::Warning,
1599 ("HandleDBClosed(): CookieStorage %p encountered error "
1600 "rebuilding db; move to "
1601 "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
1602 this, static_cast<uint32_t>(rv)));
1603 if (os) {
1604 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
1606 break;
1611 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::Read() {
1612 MOZ_ASSERT(NS_GetCurrentThread() == mThread);
1614 // Read in the data synchronously.
1615 // see IDX_NAME, etc. for parameter indexes
1616 nsCOMPtr<mozIStorageStatement> stmt;
1617 nsresult rv =
1618 mSyncConn->CreateStatement(nsLiteralCString("SELECT "
1619 "name, "
1620 "value, "
1621 "host, "
1622 "path, "
1623 "expiry, "
1624 "lastAccessed, "
1625 "creationTime, "
1626 "isSecure, "
1627 "isHttpOnly, "
1628 "originAttributes, "
1629 "sameSite, "
1630 "rawSameSite, "
1631 "schemeMap, "
1632 "isPartitionedAttributeSet "
1633 "FROM moz_cookies"),
1634 getter_AddRefs(stmt));
1636 NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
1638 if (NS_WARN_IF(!mReadArray.IsEmpty())) {
1639 mReadArray.Clear();
1641 mReadArray.SetCapacity(kMaxNumberOfCookies);
1643 nsCString baseDomain;
1644 nsCString name;
1645 nsCString value;
1646 nsCString host;
1647 nsCString path;
1648 bool hasResult;
1649 while (true) {
1650 rv = stmt->ExecuteStep(&hasResult);
1651 if (NS_WARN_IF(NS_FAILED(rv))) {
1652 mReadArray.Clear();
1653 return RESULT_RETRY;
1656 if (!hasResult) {
1657 break;
1660 stmt->GetUTF8String(IDX_HOST, host);
1662 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
1663 if (NS_FAILED(rv)) {
1664 COOKIE_LOGSTRING(LogLevel::Debug,
1665 ("Read(): Ignoring invalid host '%s'", host.get()));
1666 continue;
1669 nsAutoCString suffix;
1670 OriginAttributes attrs;
1671 stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
1672 // If PopulateFromSuffix failed we just ignore the OA attributes
1673 // that we don't support
1674 Unused << attrs.PopulateFromSuffix(suffix);
1676 CookieKey key(baseDomain, attrs);
1677 CookieDomainTuple* tuple = mReadArray.AppendElement();
1678 tuple->key = std::move(key);
1679 tuple->originAttributes = attrs;
1680 tuple->cookie = GetCookieFromRow(stmt);
1683 COOKIE_LOGSTRING(LogLevel::Debug,
1684 ("Read(): %zu cookies read", mReadArray.Length()));
1686 return RESULT_OK;
1689 // Extract data from a single result row and create an Cookie.
1690 UniquePtr<CookieStruct> CookiePersistentStorage::GetCookieFromRow(
1691 mozIStorageStatement* aRow) {
1692 nsCString name;
1693 nsCString value;
1694 nsCString host;
1695 nsCString path;
1696 DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
1697 MOZ_ASSERT(NS_SUCCEEDED(rv));
1698 rv = aRow->GetUTF8String(IDX_VALUE, value);
1699 MOZ_ASSERT(NS_SUCCEEDED(rv));
1700 rv = aRow->GetUTF8String(IDX_HOST, host);
1701 MOZ_ASSERT(NS_SUCCEEDED(rv));
1702 rv = aRow->GetUTF8String(IDX_PATH, path);
1703 MOZ_ASSERT(NS_SUCCEEDED(rv));
1705 int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
1706 int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
1707 int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
1708 bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
1709 bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
1710 int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
1711 int32_t rawSameSite = aRow->AsInt32(IDX_RAW_SAME_SITE);
1712 int32_t schemeMap = aRow->AsInt32(IDX_SCHEME_MAP);
1713 bool isPartitionedAttributeSet =
1714 0 != aRow->AsInt32(IDX_PARTITIONED_ATTRIBUTE_SET);
1716 // Create a new constCookie and assign the data.
1717 return MakeUnique<CookieStruct>(
1718 name, value, host, path, expiry, lastAccessed, creationTime, isHttpOnly,
1719 false, isSecure, isPartitionedAttributeSet, sameSite, rawSameSite,
1720 static_cast<nsICookie::schemeType>(schemeMap));
1723 void CookiePersistentStorage::EnsureInitialized() {
1724 MOZ_ASSERT(NS_IsMainThread());
1726 bool isAccumulated = false;
1728 if (!mInitialized) {
1729 TimeStamp startBlockTime = TimeStamp::Now();
1730 MonitorAutoLock lock(mMonitor);
1732 while (!mInitialized) {
1733 mMonitor.Wait();
1736 Telemetry::AccumulateTimeDelta(
1737 Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, startBlockTime);
1738 Telemetry::Accumulate(
1739 Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
1740 isAccumulated = true;
1741 } else if (!mEndInitDBConn.IsNull()) {
1742 // We didn't block main thread, and here comes the first cookie request.
1743 // Collect how close we're going to block main thread.
1744 Telemetry::Accumulate(
1745 Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS,
1746 (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds());
1747 // Nullify the timestamp so wo don't accumulate this telemetry probe again.
1748 mEndInitDBConn = TimeStamp();
1749 isAccumulated = true;
1750 } else if (!mInitializedDBConn) {
1751 // A request comes while we finished cookie thread task and InitDBConn is
1752 // on the way from cookie thread to main thread. We're very close to block
1753 // main thread.
1754 Telemetry::Accumulate(
1755 Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
1756 isAccumulated = true;
1759 if (!mInitializedDBConn) {
1760 InitDBConn();
1761 if (isAccumulated) {
1762 // Nullify the timestamp so wo don't accumulate this telemetry probe
1763 // again.
1764 mEndInitDBConn = TimeStamp();
1769 void CookiePersistentStorage::InitDBConn() {
1770 MOZ_ASSERT(NS_IsMainThread());
1772 // We should skip InitDBConn if we close profile during initializing
1773 // CookieStorages and then InitDBConn is called after we close the
1774 // CookieStorages.
1775 if (!mInitialized || mInitializedDBConn) {
1776 return;
1779 nsCOMPtr<nsIURI> dummyUri;
1780 nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://example.com");
1781 MOZ_ASSERT(NS_SUCCEEDED(rv));
1782 nsTArray<RefPtr<Cookie>> cleanupCookies;
1784 for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
1785 CookieDomainTuple& tuple = mReadArray[i];
1786 MOZ_ASSERT(!tuple.cookie->isSession());
1788 // filter invalid non-ipv4 host ending in number from old db values
1789 nsCOMPtr<nsIURIMutator> outMut;
1790 nsCOMPtr<nsIURIMutator> dummyMut;
1791 rv = dummyUri->Mutate(getter_AddRefs(dummyMut));
1792 MOZ_ASSERT(NS_SUCCEEDED(rv));
1793 rv = dummyMut->SetHost(tuple.cookie->host(), getter_AddRefs(outMut));
1795 if (NS_FAILED(rv)) {
1796 COOKIE_LOGSTRING(LogLevel::Debug, ("Removing cookie from db with "
1797 "newly invalid hostname: '%s'",
1798 tuple.cookie->host().get()));
1799 RefPtr<Cookie> cookie =
1800 Cookie::Create(*tuple.cookie, tuple.originAttributes);
1801 cleanupCookies.AppendElement(cookie);
1802 continue;
1805 // CreateValidated fixes up the creation and lastAccessed times.
1806 // If the DB is corrupted and the timestaps are far away in the future
1807 // we don't want the creation timestamp to update gLastCreationTime
1808 // as that would contaminate all the next creation times.
1809 // We fix up these dates to not be later than the current time.
1810 // The downside is that if the user sets the date far away in the past
1811 // then back to the current date, those cookies will be stale,
1812 // but if we don't fix their dates, those cookies might never be
1813 // evicted.
1814 RefPtr<Cookie> cookie =
1815 Cookie::CreateValidated(*tuple.cookie, tuple.originAttributes);
1816 AddCookieToList(tuple.key.mBaseDomain, tuple.key.mOriginAttributes, cookie);
1819 if (NS_FAILED(InitDBConnInternal())) {
1820 COOKIE_LOGSTRING(LogLevel::Warning,
1821 ("InitDBConn(): retrying InitDBConnInternal()"));
1822 CleanupCachedStatements();
1823 CleanupDBConnection();
1824 if (NS_FAILED(InitDBConnInternal())) {
1825 COOKIE_LOGSTRING(
1826 LogLevel::Warning,
1827 ("InitDBConn(): InitDBConnInternal() failed, closing connection"));
1829 // Game over, clean the connections.
1830 CleanupCachedStatements();
1831 CleanupDBConnection();
1834 mInitializedDBConn = true;
1836 COOKIE_LOGSTRING(LogLevel::Debug,
1837 ("InitDBConn(): mInitializedDBConn = true"));
1838 mEndInitDBConn = TimeStamp::Now();
1840 for (const auto& cookie : cleanupCookies) {
1841 RemoveCookieFromDB(*cookie);
1844 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1845 if (os) {
1846 os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
1847 mReadArray.Clear();
1851 nsresult CookiePersistentStorage::InitDBConnInternal() {
1852 MOZ_ASSERT(NS_IsMainThread());
1854 nsresult rv = mStorageService->OpenUnsharedDatabase(
1855 mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
1856 getter_AddRefs(mDBConn));
1857 NS_ENSURE_SUCCESS(rv, rv);
1859 // Set up our listeners.
1860 mInsertListener = new InsertCookieDBListener(this);
1861 mUpdateListener = new UpdateCookieDBListener(this);
1862 mRemoveListener = new RemoveCookieDBListener(this);
1863 mCloseListener = new CloseCookieDBListener(this);
1865 // Grow cookie db in 512KB increments
1866 mDBConn->SetGrowthIncrement(512 * 1024, ""_ns);
1868 // make operations on the table asynchronous, for performance
1869 mDBConn->ExecuteSimpleSQL("PRAGMA synchronous = OFF"_ns);
1871 // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
1872 // 16 pages (around 500KB).
1873 mDBConn->ExecuteSimpleSQL(nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
1874 "PRAGMA journal_mode = WAL"));
1875 mDBConn->ExecuteSimpleSQL("PRAGMA wal_autocheckpoint = 16"_ns);
1877 // cache frequently used statements (for insertion, deletion, and updating)
1878 rv = mDBConn->CreateAsyncStatement(
1879 nsLiteralCString("INSERT INTO moz_cookies ("
1880 "originAttributes, "
1881 "name, "
1882 "value, "
1883 "host, "
1884 "path, "
1885 "expiry, "
1886 "lastAccessed, "
1887 "creationTime, "
1888 "isSecure, "
1889 "isHttpOnly, "
1890 "sameSite, "
1891 "rawSameSite, "
1892 "schemeMap, "
1893 "isPartitionedAttributeSet "
1894 ") VALUES ("
1895 ":originAttributes, "
1896 ":name, "
1897 ":value, "
1898 ":host, "
1899 ":path, "
1900 ":expiry, "
1901 ":lastAccessed, "
1902 ":creationTime, "
1903 ":isSecure, "
1904 ":isHttpOnly, "
1905 ":sameSite, "
1906 ":rawSameSite, "
1907 ":schemeMap, "
1908 ":isPartitionedAttributeSet "
1909 ")"),
1910 getter_AddRefs(mStmtInsert));
1911 NS_ENSURE_SUCCESS(rv, rv);
1913 rv = mDBConn->CreateAsyncStatement(
1914 nsLiteralCString("DELETE FROM moz_cookies "
1915 "WHERE name = :name AND host = :host AND path = :path "
1916 "AND originAttributes = :originAttributes"),
1917 getter_AddRefs(mStmtDelete));
1918 NS_ENSURE_SUCCESS(rv, rv);
1920 rv = mDBConn->CreateAsyncStatement(
1921 nsLiteralCString("UPDATE moz_cookies SET lastAccessed = :lastAccessed "
1922 "WHERE name = :name AND host = :host AND path = :path "
1923 "AND originAttributes = :originAttributes"),
1924 getter_AddRefs(mStmtUpdate));
1925 return rv;
1928 // Sets the schema version and creates the moz_cookies table.
1929 nsresult CookiePersistentStorage::CreateTableWorker(const char* aName) {
1930 // Create the table.
1931 // We default originAttributes to empty string: this is so if users revert to
1932 // an older Firefox version that doesn't know about this field, any cookies
1933 // set will still work once they upgrade back.
1934 nsAutoCString command("CREATE TABLE ");
1935 command.Append(aName);
1936 command.AppendLiteral(
1937 " ("
1938 "id INTEGER PRIMARY KEY, "
1939 "originAttributes TEXT NOT NULL DEFAULT '', "
1940 "name TEXT, "
1941 "value TEXT, "
1942 "host TEXT, "
1943 "path TEXT, "
1944 "expiry INTEGER, "
1945 "lastAccessed INTEGER, "
1946 "creationTime INTEGER, "
1947 "isSecure INTEGER, "
1948 "isHttpOnly INTEGER, "
1949 "inBrowserElement INTEGER DEFAULT 0, "
1950 "sameSite INTEGER DEFAULT 0, "
1951 "rawSameSite INTEGER DEFAULT 0, "
1952 "schemeMap INTEGER DEFAULT 0, "
1953 "isPartitionedAttributeSet INTEGER DEFAULT 0, "
1954 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
1955 ")");
1956 return mSyncConn->ExecuteSimpleSQL(command);
1959 // Sets the schema version and creates the moz_cookies table.
1960 nsresult CookiePersistentStorage::CreateTable() {
1961 // Set the schema version, before creating the table.
1962 nsresult rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
1963 if (NS_FAILED(rv)) {
1964 return rv;
1967 rv = CreateTableWorker("moz_cookies");
1968 if (NS_FAILED(rv)) {
1969 return rv;
1972 return NS_OK;
1975 // Sets the schema version and creates the moz_cookies table.
1976 nsresult CookiePersistentStorage::CreateTableForSchemaVersion6() {
1977 // Set the schema version, before creating the table.
1978 nsresult rv = mSyncConn->SetSchemaVersion(6);
1979 if (NS_FAILED(rv)) {
1980 return rv;
1983 // Create the table.
1984 // We default originAttributes to empty string: this is so if users revert to
1985 // an older Firefox version that doesn't know about this field, any cookies
1986 // set will still work once they upgrade back.
1987 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
1988 "CREATE TABLE moz_cookies ("
1989 "id INTEGER PRIMARY KEY, "
1990 "baseDomain TEXT, "
1991 "originAttributes TEXT NOT NULL DEFAULT '', "
1992 "name TEXT, "
1993 "value TEXT, "
1994 "host TEXT, "
1995 "path TEXT, "
1996 "expiry INTEGER, "
1997 "lastAccessed INTEGER, "
1998 "creationTime INTEGER, "
1999 "isSecure INTEGER, "
2000 "isHttpOnly INTEGER, "
2001 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
2002 ")"));
2003 if (NS_FAILED(rv)) {
2004 return rv;
2007 // Create an index on baseDomain.
2008 return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
2009 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
2010 "originAttributes)"));
2013 // Sets the schema version and creates the moz_cookies table.
2014 nsresult CookiePersistentStorage::CreateTableForSchemaVersion5() {
2015 // Set the schema version, before creating the table.
2016 nsresult rv = mSyncConn->SetSchemaVersion(5);
2017 if (NS_FAILED(rv)) {
2018 return rv;
2021 // Create the table. We default appId/inBrowserElement to 0: this is so if
2022 // users revert to an older Firefox version that doesn't know about these
2023 // fields, any cookies set will still work once they upgrade back.
2024 rv = mSyncConn->ExecuteSimpleSQL(
2025 nsLiteralCString("CREATE TABLE moz_cookies ("
2026 "id INTEGER PRIMARY KEY, "
2027 "baseDomain TEXT, "
2028 "appId INTEGER DEFAULT 0, "
2029 "inBrowserElement INTEGER DEFAULT 0, "
2030 "name TEXT, "
2031 "value TEXT, "
2032 "host TEXT, "
2033 "path TEXT, "
2034 "expiry INTEGER, "
2035 "lastAccessed INTEGER, "
2036 "creationTime INTEGER, "
2037 "isSecure INTEGER, "
2038 "isHttpOnly INTEGER, "
2039 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, "
2040 "appId, inBrowserElement)"
2041 ")"));
2042 if (NS_FAILED(rv)) {
2043 return rv;
2046 // Create an index on baseDomain.
2047 return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
2048 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
2049 "appId, "
2050 "inBrowserElement)"));
2053 nsresult CookiePersistentStorage::RunInTransaction(
2054 nsICookieTransactionCallback* aCallback) {
2055 if (NS_WARN_IF(!mDBConn)) {
2056 return NS_ERROR_NOT_AVAILABLE;
2059 mozStorageTransaction transaction(mDBConn, true);
2061 // XXX Handle the error, bug 1696130.
2062 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
2064 if (NS_FAILED(aCallback->Callback())) {
2065 Unused << transaction.Rollback();
2066 return NS_ERROR_FAILURE;
2069 return NS_OK;
2072 // purges expired and old cookies in a batch operation.
2073 already_AddRefed<nsIArray> CookiePersistentStorage::PurgeCookies(
2074 int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
2075 int64_t aCookiePurgeAge) {
2076 // Create a params array to batch the removals. This is OK here because
2077 // all the removals are in order, and there are no interleaved additions.
2078 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
2079 if (mDBConn) {
2080 mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
2083 RefPtr<CookiePersistentStorage> self = this;
2085 return PurgeCookiesWithCallbacks(
2086 aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge,
2087 [paramsArray, self](const CookieListIter& aIter) {
2088 self->PrepareCookieRemoval(*aIter.Cookie(), paramsArray);
2089 self->RemoveCookieFromListInternal(aIter);
2091 [paramsArray, self]() {
2092 if (paramsArray) {
2093 self->DeleteFromDB(paramsArray);
2098 void CookiePersistentStorage::CollectCookieJarSizeData() {
2099 COOKIE_LOGSTRING(LogLevel::Debug,
2100 ("CookiePersistentStorage::CollectCookieJarSizeData"));
2102 uint32_t sumPartitioned = 0;
2103 uint32_t sumUnpartitioned = 0;
2104 for (const auto& cookieEntry : mHostTable) {
2105 if (cookieEntry.IsPartitioned()) {
2106 uint16_t cePartitioned = cookieEntry.GetCookies().Length();
2107 sumPartitioned += cePartitioned;
2108 mozilla::glean::networking::cookie_count_part_by_key
2109 .AccumulateSingleSample(cePartitioned);
2110 } else {
2111 uint16_t ceUnpartitioned = cookieEntry.GetCookies().Length();
2112 sumUnpartitioned += ceUnpartitioned;
2113 mozilla::glean::networking::cookie_count_unpart_by_key
2114 .AccumulateSingleSample(ceUnpartitioned);
2118 mozilla::glean::networking::cookie_count_total.AccumulateSingleSample(
2119 mCookieCount);
2120 mozilla::glean::networking::cookie_count_partitioned.AccumulateSingleSample(
2121 sumPartitioned);
2122 mozilla::glean::networking::cookie_count_unpartitioned.AccumulateSingleSample(
2123 sumUnpartitioned);
2126 } // namespace net
2127 } // namespace mozilla