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