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