CookiePersistentStorage.cpp (91707B)
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 #include "CookieService.h" 11 #include "CookieValidation.h" 12 13 #include "mozilla/FileUtils.h" 14 #include "mozilla/StaticPrefs_network.h" 15 #include "mozilla/glean/NetwerkMetrics.h" 16 #include "mozilla/ScopeExit.h" 17 #include "mozIStorageAsyncStatement.h" 18 #include "mozIStorageError.h" 19 #include "mozIStorageFunction.h" 20 #include "mozIStorageService.h" 21 #include "mozStorageHelper.h" 22 #include "nsAppDirectoryServiceDefs.h" 23 #include "nsICookieNotification.h" 24 #include "nsIEffectiveTLDService.h" 25 #include "nsILineInputStream.h" 26 #include "nsIURIMutator.h" 27 #include "nsNetUtil.h" 28 #include "nsVariant.h" 29 #include "prprf.h" 30 31 constexpr auto COOKIES_SCHEMA_VERSION = 17; 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_INMSEC = 4; 39 constexpr auto IDX_LAST_ACCESSED_INUSEC = 5; 40 constexpr auto IDX_CREATION_TIME_INUSEC = 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_SCHEME_MAP = 11; 46 constexpr auto IDX_PARTITIONED_ATTRIBUTE_SET = 12; 47 constexpr auto IDX_UPDATE_TIME_INUSEC = 13; 48 49 #define COOKIES_FILE "cookies.sqlite" 50 51 namespace mozilla { 52 namespace net { 53 54 namespace { 55 56 void BindCookieParameters(mozIStorageBindingParamsArray* aParamsArray, 57 const CookieKey& aKey, const Cookie* aCookie) { 58 NS_ASSERTION(aParamsArray, 59 "Null params array passed to BindCookieParameters!"); 60 NS_ASSERTION(aCookie, "Null cookie passed to BindCookieParameters!"); 61 62 // Use the asynchronous binding methods to ensure that we do not acquire the 63 // database lock. 64 nsCOMPtr<mozIStorageBindingParams> params; 65 DebugOnly<nsresult> rv = 66 aParamsArray->NewBindingParams(getter_AddRefs(params)); 67 MOZ_ASSERT(NS_SUCCEEDED(rv)); 68 69 nsAutoCString suffix; 70 aKey.mOriginAttributes.CreateSuffix(suffix); 71 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix); 72 MOZ_ASSERT(NS_SUCCEEDED(rv)); 73 74 rv = params->BindUTF8StringByName("name"_ns, aCookie->Name()); 75 MOZ_ASSERT(NS_SUCCEEDED(rv)); 76 77 rv = params->BindUTF8StringByName("value"_ns, aCookie->Value()); 78 MOZ_ASSERT(NS_SUCCEEDED(rv)); 79 80 rv = params->BindUTF8StringByName("host"_ns, aCookie->Host()); 81 MOZ_ASSERT(NS_SUCCEEDED(rv)); 82 83 rv = params->BindUTF8StringByName("path"_ns, aCookie->Path()); 84 MOZ_ASSERT(NS_SUCCEEDED(rv)); 85 86 rv = params->BindInt64ByName("expiry"_ns, aCookie->ExpiryInMSec()); 87 MOZ_ASSERT(NS_SUCCEEDED(rv)); 88 89 rv = 90 params->BindInt64ByName("lastAccessed"_ns, aCookie->LastAccessedInUSec()); 91 MOZ_ASSERT(NS_SUCCEEDED(rv)); 92 93 rv = 94 params->BindInt64ByName("creationTime"_ns, aCookie->CreationTimeInUSec()); 95 MOZ_ASSERT(NS_SUCCEEDED(rv)); 96 97 rv = params->BindInt32ByName("isSecure"_ns, aCookie->IsSecure()); 98 MOZ_ASSERT(NS_SUCCEEDED(rv)); 99 100 rv = params->BindInt32ByName("isHttpOnly"_ns, aCookie->IsHttpOnly()); 101 MOZ_ASSERT(NS_SUCCEEDED(rv)); 102 103 rv = params->BindInt32ByName("sameSite"_ns, aCookie->SameSite()); 104 MOZ_ASSERT(NS_SUCCEEDED(rv)); 105 106 rv = params->BindInt32ByName("schemeMap"_ns, aCookie->SchemeMap()); 107 MOZ_ASSERT(NS_SUCCEEDED(rv)); 108 109 rv = params->BindInt32ByName("isPartitionedAttributeSet"_ns, 110 aCookie->RawIsPartitioned()); 111 MOZ_ASSERT(NS_SUCCEEDED(rv)); 112 113 rv = params->BindInt64ByName("updateTime"_ns, aCookie->UpdateTimeInUSec()); 114 MOZ_ASSERT(NS_SUCCEEDED(rv)); 115 116 // Bind the params to the array. 117 rv = aParamsArray->AddParams(params); 118 MOZ_ASSERT(NS_SUCCEEDED(rv)); 119 } 120 121 class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction { 122 ~ConvertAppIdToOriginAttrsSQLFunction() = default; 123 124 NS_DECL_ISUPPORTS 125 NS_DECL_MOZISTORAGEFUNCTION 126 }; 127 128 NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction); 129 130 NS_IMETHODIMP 131 ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall( 132 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 133 nsresult rv; 134 OriginAttributes attrs; 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 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 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(false); 201 NS_ENSURE_SUCCESS(rv, rv); 202 203 outVar.forget(aResult); 204 return NS_OK; 205 } 206 207 class FetchPartitionKeyFromOAsSQLFunction final : public mozIStorageFunction { 208 ~FetchPartitionKeyFromOAsSQLFunction() = default; 209 210 NS_DECL_ISUPPORTS 211 NS_DECL_MOZISTORAGEFUNCTION 212 }; 213 214 NS_IMPL_ISUPPORTS(FetchPartitionKeyFromOAsSQLFunction, mozIStorageFunction); 215 216 NS_IMETHODIMP 217 FetchPartitionKeyFromOAsSQLFunction::OnFunctionCall( 218 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 219 nsresult rv; 220 221 nsAutoCString suffix; 222 rv = aFunctionArguments->GetUTF8String(0, suffix); 223 NS_ENSURE_SUCCESS(rv, rv); 224 225 OriginAttributes attrsFromSuffix; 226 bool success = attrsFromSuffix.PopulateFromSuffix(suffix); 227 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); 228 229 RefPtr<nsVariant> outVar(new nsVariant()); 230 rv = outVar->SetAsAString(attrsFromSuffix.mPartitionKey); 231 NS_ENSURE_SUCCESS(rv, rv); 232 233 outVar.forget(aResult); 234 235 return NS_OK; 236 } 237 238 class UpdateOAsWithPartitionHostSQLFunction final : public mozIStorageFunction { 239 ~UpdateOAsWithPartitionHostSQLFunction() = default; 240 241 NS_DECL_ISUPPORTS 242 NS_DECL_MOZISTORAGEFUNCTION 243 }; 244 245 NS_IMPL_ISUPPORTS(UpdateOAsWithPartitionHostSQLFunction, mozIStorageFunction); 246 247 NS_IMETHODIMP 248 UpdateOAsWithPartitionHostSQLFunction::OnFunctionCall( 249 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 250 nsresult rv; 251 252 nsAutoCString formattedOriginAttributes; 253 rv = aFunctionArguments->GetUTF8String(0, formattedOriginAttributes); 254 NS_ENSURE_SUCCESS(rv, rv); 255 256 nsAutoCString partitionKeyHost; 257 rv = aFunctionArguments->GetUTF8String(1, partitionKeyHost); 258 NS_ENSURE_SUCCESS(rv, rv); 259 260 OriginAttributes attrsFromSuffix; 261 bool success = attrsFromSuffix.PopulateFromSuffix(formattedOriginAttributes); 262 // On failure, do not alter the OA. 263 if (!success) { 264 RefPtr<nsVariant> outVar(new nsVariant()); 265 rv = outVar->SetAsACString(formattedOriginAttributes); 266 NS_ENSURE_SUCCESS(rv, rv); 267 outVar.forget(aResult); 268 return NS_OK; 269 } 270 271 // This is a bit hacky. However, CHIPS cookies can only be set in secure 272 // contexts. So, the scheme has to be https. 273 nsAutoCString schemeHost; 274 schemeHost.AssignLiteral("https://"); 275 276 if (*partitionKeyHost.get() == '.') { 277 schemeHost.Append(nsDependentCSubstring(partitionKeyHost, 1)); 278 } else { 279 schemeHost.Append(partitionKeyHost); 280 } 281 282 nsCOMPtr<nsIURI> uri; 283 rv = NS_NewURI(getter_AddRefs(uri), schemeHost); 284 // On failure, do not alter the OA. 285 if (NS_FAILED(rv)) { 286 RefPtr<nsVariant> outVar(new nsVariant()); 287 rv = outVar->SetAsACString(formattedOriginAttributes); 288 NS_ENSURE_SUCCESS(rv, rv); 289 outVar.forget(aResult); 290 return NS_OK; 291 } 292 293 attrsFromSuffix.SetPartitionKey(uri, false); 294 attrsFromSuffix.CreateSuffix(formattedOriginAttributes); 295 296 RefPtr<nsVariant> outVar(new nsVariant()); 297 rv = outVar->SetAsACString(formattedOriginAttributes); 298 NS_ENSURE_SUCCESS(rv, rv); 299 outVar.forget(aResult); 300 return NS_OK; 301 } 302 303 /****************************************************************************** 304 * DBListenerErrorHandler impl: 305 * Parent class for our async storage listeners that handles the logging of 306 * errors. 307 ******************************************************************************/ 308 class DBListenerErrorHandler : public mozIStorageStatementCallback { 309 protected: 310 explicit DBListenerErrorHandler(CookiePersistentStorage* dbState) 311 : mStorage(dbState) {} 312 RefPtr<CookiePersistentStorage> mStorage; 313 virtual const char* GetOpType() = 0; 314 315 public: 316 NS_IMETHOD HandleError(mozIStorageError* aError) override { 317 if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) { 318 int32_t result = -1; 319 aError->GetResult(&result); 320 321 nsAutoCString message; 322 aError->GetMessage(message); 323 COOKIE_LOGSTRING( 324 LogLevel::Warning, 325 ("DBListenerErrorHandler::HandleError(): Error %d occurred while " 326 "performing operation '%s' with message '%s'; rebuilding database.", 327 result, GetOpType(), message.get())); 328 } 329 330 // Rebuild the database. 331 mStorage->HandleCorruptDB(); 332 333 return NS_OK; 334 } 335 }; 336 337 /****************************************************************************** 338 * InsertCookieDBListener impl: 339 * mozIStorageStatementCallback used to track asynchronous insertion operations. 340 ******************************************************************************/ 341 class InsertCookieDBListener final : public DBListenerErrorHandler { 342 private: 343 const char* GetOpType() override { return "INSERT"; } 344 345 ~InsertCookieDBListener() = default; 346 347 public: 348 NS_DECL_ISUPPORTS 349 350 explicit InsertCookieDBListener(CookiePersistentStorage* dbState) 351 : DBListenerErrorHandler(dbState) {} 352 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override { 353 MOZ_ASSERT_UNREACHABLE( 354 "Unexpected call to " 355 "InsertCookieDBListener::HandleResult"); 356 return NS_OK; 357 } 358 NS_IMETHOD HandleCompletion(uint16_t aReason) override { 359 // If we were rebuilding the db and we succeeded, make our mCorruptFlag say 360 // so. 361 if (mStorage->GetCorruptFlag() == CookiePersistentStorage::REBUILDING && 362 aReason == mozIStorageStatementCallback::REASON_FINISHED) { 363 COOKIE_LOGSTRING( 364 LogLevel::Debug, 365 ("InsertCookieDBListener::HandleCompletion(): rebuild complete")); 366 mStorage->SetCorruptFlag(CookiePersistentStorage::OK); 367 } 368 369 // This notification is just for testing. 370 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 371 if (os) { 372 os->NotifyObservers(nullptr, "cookie-saved-on-disk", nullptr); 373 } 374 375 return NS_OK; 376 } 377 }; 378 379 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback) 380 381 /****************************************************************************** 382 * UpdateCookieDBListener impl: 383 * mozIStorageStatementCallback used to track asynchronous update operations. 384 ******************************************************************************/ 385 class UpdateCookieDBListener final : public DBListenerErrorHandler { 386 private: 387 const char* GetOpType() override { return "UPDATE"; } 388 389 ~UpdateCookieDBListener() = default; 390 391 public: 392 NS_DECL_ISUPPORTS 393 394 explicit UpdateCookieDBListener(CookiePersistentStorage* dbState) 395 : DBListenerErrorHandler(dbState) {} 396 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override { 397 MOZ_ASSERT_UNREACHABLE( 398 "Unexpected call to " 399 "UpdateCookieDBListener::HandleResult"); 400 return NS_OK; 401 } 402 NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; } 403 }; 404 405 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback) 406 407 /****************************************************************************** 408 * RemoveCookieDBListener impl: 409 * mozIStorageStatementCallback used to track asynchronous removal operations. 410 ******************************************************************************/ 411 class RemoveCookieDBListener final : public DBListenerErrorHandler { 412 private: 413 const char* GetOpType() override { return "REMOVE"; } 414 415 ~RemoveCookieDBListener() = default; 416 417 public: 418 NS_DECL_ISUPPORTS 419 420 explicit RemoveCookieDBListener(CookiePersistentStorage* dbState) 421 : DBListenerErrorHandler(dbState) {} 422 NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override { 423 MOZ_ASSERT_UNREACHABLE( 424 "Unexpected call to " 425 "RemoveCookieDBListener::HandleResult"); 426 return NS_OK; 427 } 428 NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; } 429 }; 430 431 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback) 432 433 /****************************************************************************** 434 * CloseCookieDBListener imp: 435 * Static mozIStorageCompletionCallback used to notify when the database is 436 * successfully closed. 437 ******************************************************************************/ 438 class CloseCookieDBListener final : public mozIStorageCompletionCallback { 439 ~CloseCookieDBListener() = default; 440 441 public: 442 explicit CloseCookieDBListener(CookiePersistentStorage* dbState) 443 : mStorage(dbState) {} 444 RefPtr<CookiePersistentStorage> mStorage; 445 NS_DECL_ISUPPORTS 446 447 NS_IMETHOD Complete(nsresult /*status*/, nsISupports* /*value*/) override { 448 mStorage->HandleDBClosed(); 449 return NS_OK; 450 } 451 }; 452 453 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback) 454 455 static nsLiteralCString ValidationErrorToLabel( 456 nsICookieValidation::ValidationError aError) { 457 switch (aError) { 458 case nsICookieValidation::eOK: 459 return "eOK"_ns; 460 case nsICookieValidation::eRejectedEmptyNameAndValue: 461 return "eRejectedEmptyNameAndValue"_ns; 462 case nsICookieValidation::eRejectedNameValueOversize: 463 return "eRejectedNameValueOversize"_ns; 464 case nsICookieValidation::eRejectedInvalidCharName: 465 return "eRejectedInvalidCharName"_ns; 466 case nsICookieValidation::eRejectedInvalidCharValue: 467 return "eRejectedInvalidCharValue"_ns; 468 case nsICookieValidation::eRejectedInvalidPath: 469 return "eRejectedInvalidPath"_ns; 470 case nsICookieValidation::eRejectedInvalidDomain: 471 return "eRejectedInvalidDomain"_ns; 472 case nsICookieValidation::eRejectedInvalidPrefix: 473 return "eRejectedInvalidPrefix"_ns; 474 case nsICookieValidation::eRejectedNoneRequiresSecure: 475 return "eRejectedNoneRequiresSecure"_ns; 476 case nsICookieValidation::eRejectedPartitionedRequiresSecure: 477 return "eRejectedPartitionedRequiresSecure"_ns; 478 case nsICookieValidation::eRejectedHttpOnlyButFromScript: 479 return "eRejectedHttpOnlyButFromScript"_ns; 480 case nsICookieValidation::eRejectedSecureButNonHttps: 481 return "eRejectedSecureButNonHttps"_ns; 482 case nsICookieValidation::eRejectedForNonSameSiteness: 483 return "eRejectedForNonSameSiteness"_ns; 484 case nsICookieValidation::eRejectedAttributePathOversize: 485 return "eRejectedAttributePathOversize"_ns; 486 case nsICookieValidation::eRejectedAttributeDomainOversize: 487 return "eRejectedAttributeDomainOversize"_ns; 488 case nsICookieValidation::eRejectedAttributeExpiryOversize: 489 return "eRejectedAttributeExpiryOversize"_ns; 490 default: 491 return "eOK"_ns; 492 } 493 } 494 495 } // namespace 496 497 // static 498 already_AddRefed<CookiePersistentStorage> CookiePersistentStorage::Create() { 499 RefPtr<CookiePersistentStorage> storage = new CookiePersistentStorage(); 500 storage->Init(); 501 storage->Activate(); 502 503 return storage.forget(); 504 } 505 506 CookiePersistentStorage::CookiePersistentStorage() 507 : mMonitor("CookiePersistentStorage"), 508 mInitialized(false), 509 mCorruptFlag(OK) {} 510 511 void CookiePersistentStorage::NotifyChangedInternal( 512 nsICookieNotification* aNotification, bool aOldCookieIsSession) { 513 MOZ_ASSERT(aNotification); 514 // Notify for topic "session-cookie-changed" to update the copy of session 515 // cookies in session restore component. 516 517 nsICookieNotification::Action action = aNotification->GetAction(); 518 519 // Filter out notifications for individual non-session cookies. 520 if (action == nsICookieNotification::COOKIE_CHANGED || 521 action == nsICookieNotification::COOKIE_DELETED || 522 action == nsICookieNotification::COOKIE_ADDED) { 523 nsCOMPtr<nsICookie> xpcCookie; 524 DebugOnly<nsresult> rv = 525 aNotification->GetCookie(getter_AddRefs(xpcCookie)); 526 MOZ_ASSERT(NS_SUCCEEDED(rv) && xpcCookie); 527 const Cookie& cookie = xpcCookie->AsCookie(); 528 if (!cookie.IsSession() && !aOldCookieIsSession) { 529 return; 530 } 531 } 532 533 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 534 if (os) { 535 os->NotifyObservers(aNotification, "session-cookie-changed", u""); 536 } 537 } 538 539 void CookiePersistentStorage::RemoveAllInternal() { 540 // clear the cookie file 541 if (mDBConn) { 542 nsCOMPtr<mozIStorageAsyncStatement> stmt; 543 nsresult rv = mDBConn->CreateAsyncStatement("DELETE FROM moz_cookies"_ns, 544 getter_AddRefs(stmt)); 545 if (NS_SUCCEEDED(rv)) { 546 nsCOMPtr<mozIStoragePendingStatement> handle; 547 rv = stmt->ExecuteAsync(mRemoveListener, getter_AddRefs(handle)); 548 MOZ_ASSERT(NS_SUCCEEDED(rv)); 549 } else { 550 // Recreate the database. 551 COOKIE_LOGSTRING(LogLevel::Debug, 552 ("RemoveAll(): corruption detected with rv 0x%" PRIx32, 553 static_cast<uint32_t>(rv))); 554 HandleCorruptDB(); 555 } 556 } 557 } 558 559 void CookiePersistentStorage::HandleCorruptDB() { 560 COOKIE_LOGSTRING(LogLevel::Debug, 561 ("HandleCorruptDB(): CookieStorage %p has mCorruptFlag %u", 562 this, mCorruptFlag)); 563 564 // Mark the database corrupt, so the close listener can begin reconstructing 565 // it. 566 switch (mCorruptFlag) { 567 case OK: { 568 // Move to 'closing' state. 569 mCorruptFlag = CLOSING_FOR_REBUILD; 570 571 CleanupCachedStatements(); 572 mDBConn->AsyncClose(mCloseListener); 573 CleanupDBConnection(); 574 break; 575 } 576 case CLOSING_FOR_REBUILD: { 577 // We had an error while waiting for close completion. That's OK, just 578 // ignore it -- we're rebuilding anyway. 579 return; 580 } 581 case REBUILDING: { 582 // We had an error while rebuilding the DB. Game over. Close the database 583 // and let the close handler do nothing; then we'll move it out of the 584 // way. 585 CleanupCachedStatements(); 586 if (mDBConn) { 587 mDBConn->AsyncClose(mCloseListener); 588 } 589 CleanupDBConnection(); 590 break; 591 } 592 } 593 } 594 595 void CookiePersistentStorage::RemoveCookiesWithOriginAttributes( 596 const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) { 597 mozStorageTransaction transaction(mDBConn, false); 598 599 // XXX Handle the error, bug 1696130. 600 (void)NS_WARN_IF(NS_FAILED(transaction.Start())); 601 602 CookieStorage::RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain); 603 604 DebugOnly<nsresult> rv = transaction.Commit(); 605 MOZ_ASSERT(NS_SUCCEEDED(rv)); 606 } 607 608 void CookiePersistentStorage::RemoveCookiesFromExactHost( 609 const nsACString& aHost, const nsACString& aBaseDomain, 610 const OriginAttributesPattern& aPattern) { 611 mozStorageTransaction transaction(mDBConn, false); 612 613 // XXX Handle the error, bug 1696130. 614 (void)NS_WARN_IF(NS_FAILED(transaction.Start())); 615 616 CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern); 617 618 DebugOnly<nsresult> rv = transaction.Commit(); 619 MOZ_ASSERT(NS_SUCCEEDED(rv)); 620 } 621 622 void CookiePersistentStorage::RemoveCookieFromDB(const Cookie& aCookie) { 623 // if it's a non-session cookie, remove it from the db 624 if (aCookie.IsSession() || !mDBConn) { 625 return; 626 } 627 628 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; 629 mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray)); 630 631 PrepareCookieRemoval(aCookie, paramsArray); 632 633 DebugOnly<nsresult> rv = mStmtDelete->BindParameters(paramsArray); 634 MOZ_ASSERT(NS_SUCCEEDED(rv)); 635 636 nsCOMPtr<mozIStoragePendingStatement> handle; 637 rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle)); 638 MOZ_ASSERT(NS_SUCCEEDED(rv)); 639 } 640 641 void CookiePersistentStorage::PrepareCookieRemoval( 642 const Cookie& aCookie, mozIStorageBindingParamsArray* aParamsArray) { 643 // if it's a non-session cookie, remove it from the db 644 if (aCookie.IsSession() || !mDBConn) { 645 return; 646 } 647 648 nsCOMPtr<mozIStorageBindingParams> params; 649 aParamsArray->NewBindingParams(getter_AddRefs(params)); 650 651 DebugOnly<nsresult> rv = 652 params->BindUTF8StringByName("name"_ns, aCookie.Name()); 653 MOZ_ASSERT(NS_SUCCEEDED(rv)); 654 655 rv = params->BindUTF8StringByName("host"_ns, aCookie.Host()); 656 MOZ_ASSERT(NS_SUCCEEDED(rv)); 657 658 rv = params->BindUTF8StringByName("path"_ns, aCookie.Path()); 659 MOZ_ASSERT(NS_SUCCEEDED(rv)); 660 661 nsAutoCString suffix; 662 aCookie.OriginAttributesRef().CreateSuffix(suffix); 663 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix); 664 MOZ_ASSERT(NS_SUCCEEDED(rv)); 665 666 rv = aParamsArray->AddParams(params); 667 MOZ_ASSERT(NS_SUCCEEDED(rv)); 668 } 669 670 // Null out the statements. 671 // This must be done before closing the connection. 672 void CookiePersistentStorage::CleanupCachedStatements() { 673 mStmtInsert = nullptr; 674 mStmtDelete = nullptr; 675 mStmtUpdate = nullptr; 676 } 677 678 // Null out the listeners, and the database connection itself. This 679 // will not null out the statements, cancel a pending read or 680 // asynchronously close the connection -- these must be done 681 // beforehand if necessary. 682 void CookiePersistentStorage::CleanupDBConnection() { 683 MOZ_ASSERT(!mStmtInsert, "mStmtInsert has been cleaned up"); 684 MOZ_ASSERT(!mStmtDelete, "mStmtDelete has been cleaned up"); 685 MOZ_ASSERT(!mStmtUpdate, "mStmtUpdate has been cleaned up"); 686 687 // Null out the database connections. If 'mDBConn' has not been used for any 688 // asynchronous operations yet, this will synchronously close it; otherwise, 689 // it's expected that the caller has performed an AsyncClose prior. 690 mDBConn = nullptr; 691 692 // Manually null out our listeners. This is necessary because they hold a 693 // strong ref to the CookieStorage itself. They'll stay alive until whatever 694 // statements are still executing complete. 695 mInsertListener = nullptr; 696 mUpdateListener = nullptr; 697 mRemoveListener = nullptr; 698 mCloseListener = nullptr; 699 } 700 701 void CookiePersistentStorage::Close() { 702 if (mThread) { 703 mThread->Shutdown(); 704 mThread = nullptr; 705 } 706 707 // Cleanup cached statements before we can close anything. 708 CleanupCachedStatements(); 709 710 if (mDBConn) { 711 // Asynchronously close the connection. We will null it below. 712 mDBConn->AsyncClose(mCloseListener); 713 } 714 715 CleanupDBConnection(); 716 717 mInitialized = false; 718 mInitializedDBConn = false; 719 } 720 721 void CookiePersistentStorage::StoreCookie( 722 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, 723 Cookie* aCookie) { 724 // if it's a non-session cookie and hasn't just been read from the db, write 725 // it out. 726 if (aCookie->IsSession() || !mDBConn) { 727 return; 728 } 729 730 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; 731 mStmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray)); 732 733 CookieKey key(aBaseDomain, aOriginAttributes); 734 BindCookieParameters(paramsArray, key, aCookie); 735 736 MaybeStoreCookiesToDB(paramsArray); 737 } 738 739 void CookiePersistentStorage::MaybeStoreCookiesToDB( 740 mozIStorageBindingParamsArray* aParamsArray) { 741 if (!aParamsArray) { 742 return; 743 } 744 745 uint32_t length; 746 aParamsArray->GetLength(&length); 747 if (!length) { 748 return; 749 } 750 751 DebugOnly<nsresult> rv = mStmtInsert->BindParameters(aParamsArray); 752 MOZ_ASSERT(NS_SUCCEEDED(rv)); 753 754 nsCOMPtr<mozIStoragePendingStatement> handle; 755 rv = mStmtInsert->ExecuteAsync(mInsertListener, getter_AddRefs(handle)); 756 MOZ_ASSERT(NS_SUCCEEDED(rv)); 757 } 758 759 void CookiePersistentStorage::StaleCookies( 760 const nsTArray<RefPtr<Cookie>>& aCookieList, int64_t aCurrentTimeInUsec) { 761 // Create an array of parameters to bind to our update statement. Batching 762 // is OK here since we're updating cookies with no interleaved operations. 763 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; 764 mozIStorageAsyncStatement* stmt = mStmtUpdate; 765 if (mDBConn) { 766 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); 767 } 768 769 int32_t count = aCookieList.Length(); 770 for (int32_t i = 0; i < count; ++i) { 771 Cookie* cookie = aCookieList.ElementAt(i); 772 773 if (cookie->IsStale()) { 774 UpdateCookieInList(cookie, aCurrentTimeInUsec, paramsArray); 775 } 776 } 777 // Update the database now if necessary. 778 if (paramsArray) { 779 uint32_t length; 780 paramsArray->GetLength(&length); 781 if (length) { 782 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); 783 MOZ_ASSERT(NS_SUCCEEDED(rv)); 784 785 nsCOMPtr<mozIStoragePendingStatement> handle; 786 rv = stmt->ExecuteAsync(mUpdateListener, getter_AddRefs(handle)); 787 MOZ_ASSERT(NS_SUCCEEDED(rv)); 788 } 789 } 790 } 791 792 void CookiePersistentStorage::UpdateCookieInList( 793 Cookie* aCookie, int64_t aLastAccessedInUSec, 794 mozIStorageBindingParamsArray* aParamsArray) { 795 MOZ_ASSERT(aCookie); 796 797 // udpate the lastAccessedInUSec timestamp 798 aCookie->SetLastAccessedInUSec(aLastAccessedInUSec); 799 800 // if it's a non-session cookie, update it in the db too 801 if (!aCookie->IsSession() && aParamsArray) { 802 // Create our params holder. 803 nsCOMPtr<mozIStorageBindingParams> params; 804 aParamsArray->NewBindingParams(getter_AddRefs(params)); 805 806 // Bind our parameters. 807 DebugOnly<nsresult> rv = 808 params->BindInt64ByName("lastAccessed"_ns, aLastAccessedInUSec); 809 MOZ_ASSERT(NS_SUCCEEDED(rv)); 810 811 rv = params->BindUTF8StringByName("name"_ns, aCookie->Name()); 812 MOZ_ASSERT(NS_SUCCEEDED(rv)); 813 814 rv = params->BindUTF8StringByName("host"_ns, aCookie->Host()); 815 MOZ_ASSERT(NS_SUCCEEDED(rv)); 816 817 rv = params->BindUTF8StringByName("path"_ns, aCookie->Path()); 818 MOZ_ASSERT(NS_SUCCEEDED(rv)); 819 820 nsAutoCString suffix; 821 aCookie->OriginAttributesRef().CreateSuffix(suffix); 822 rv = params->BindUTF8StringByName("originAttributes"_ns, suffix); 823 MOZ_ASSERT(NS_SUCCEEDED(rv)); 824 825 // Add our bound parameters to the array. 826 rv = aParamsArray->AddParams(params); 827 MOZ_ASSERT(NS_SUCCEEDED(rv)); 828 } 829 } 830 831 void CookiePersistentStorage::DeleteFromDB( 832 mozIStorageBindingParamsArray* aParamsArray) { 833 uint32_t length; 834 aParamsArray->GetLength(&length); 835 if (length) { 836 DebugOnly<nsresult> rv = mStmtDelete->BindParameters(aParamsArray); 837 MOZ_ASSERT(NS_SUCCEEDED(rv)); 838 839 nsCOMPtr<mozIStoragePendingStatement> handle; 840 rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle)); 841 MOZ_ASSERT(NS_SUCCEEDED(rv)); 842 } 843 } 844 845 void CookiePersistentStorage::Activate() { 846 MOZ_ASSERT(!mThread, "already have a cookie thread"); 847 848 mStorageService = do_GetService("@mozilla.org/storage/service;1"); 849 MOZ_ASSERT(mStorageService); 850 851 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 852 MOZ_ASSERT(mTLDService); 853 854 // Get our cookie file. 855 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 856 getter_AddRefs(mCookieFile)); 857 if (NS_FAILED(rv)) { 858 // We've already set up our CookieStorages appropriately; nothing more to 859 // do. 860 COOKIE_LOGSTRING(LogLevel::Warning, 861 ("InitCookieStorages(): couldn't get cookie file")); 862 863 mInitializedDBConn = true; 864 mInitialized = true; 865 return; 866 } 867 868 mCookieFile->AppendNative(nsLiteralCString(COOKIES_FILE)); 869 870 NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread))); 871 872 RefPtr<CookiePersistentStorage> self = this; 873 nsCOMPtr<nsIRunnable> runnable = 874 NS_NewRunnableFunction("CookiePersistentStorage::Activate", [self] { 875 MonitorAutoLock lock(self->mMonitor); 876 877 // Attempt to open and read the database. If TryInitDB() returns 878 // RESULT_RETRY, do so. 879 OpenDBResult result = self->TryInitDB(false); 880 if (result == RESULT_RETRY) { 881 // Database may be corrupt. Synchronously close the connection, clean 882 // up the default CookieStorage, and try again. 883 COOKIE_LOGSTRING(LogLevel::Warning, 884 ("InitCookieStorages(): retrying TryInitDB()")); 885 self->CleanupCachedStatements(); 886 self->CleanupDBConnection(); 887 result = self->TryInitDB(true); 888 if (result == RESULT_RETRY) { 889 // We're done. Change the code to failure so we clean up below. 890 result = RESULT_FAILURE; 891 } 892 } 893 894 if (result == RESULT_FAILURE) { 895 COOKIE_LOGSTRING( 896 LogLevel::Warning, 897 ("InitCookieStorages(): TryInitDB() failed, closing connection")); 898 899 // Connection failure is unrecoverable. Clean up our connection. We 900 // can run fine without persistent storage -- e.g. if there's no 901 // profile. 902 self->CleanupCachedStatements(); 903 self->CleanupDBConnection(); 904 905 // No need to initialize mDBConn 906 self->mInitializedDBConn = true; 907 } 908 909 self->mInitialized = true; 910 911 NS_DispatchToMainThread( 912 NS_NewRunnableFunction("CookiePersistentStorage::InitDBConn", 913 [self] { self->InitDBConn(); })); 914 self->mMonitor.Notify(); 915 }); 916 917 mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 918 } 919 920 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to 921 * move the existing database file out of the way and create a new one. 922 * 923 * @returns RESULT_OK if opening or creating the database succeeded; 924 * RESULT_RETRY if the database cannot be opened, is corrupt, or some 925 * other failure occurred that might be resolved by recreating the 926 * database; or RESULT_FAILED if there was an unrecoverable error and 927 * we must run without a database. 928 * 929 * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform 930 * cleanup of the default CookieStorage. 931 */ 932 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::TryInitDB( 933 bool aRecreateDB) { 934 NS_ASSERTION(!mDBConn, "nonnull mDBConn"); 935 NS_ASSERTION(!mStmtInsert, "nonnull mStmtInsert"); 936 NS_ASSERTION(!mInsertListener, "nonnull mInsertListener"); 937 NS_ASSERTION(!mSyncConn, "nonnull mSyncConn"); 938 NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread"); 939 940 // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't 941 // want to delete it outright, since it may be useful for debugging purposes, 942 // so we move it out of the way. 943 nsresult rv; 944 if (aRecreateDB) { 945 nsCOMPtr<nsIFile> backupFile; 946 mCookieFile->Clone(getter_AddRefs(backupFile)); 947 rv = backupFile->MoveToNative(nullptr, 948 nsLiteralCString(COOKIES_FILE ".bak")); 949 NS_ENSURE_SUCCESS(rv, RESULT_FAILURE); 950 } 951 952 // This block provides scope for the Telemetry AutoTimer 953 { 954 auto timer = glean::network_cookies::sqlite_open_readahead.Measure(); 955 ReadAheadFile(mCookieFile); 956 957 // open a connection to the cookie database, and only cache our connection 958 // and statements upon success. The connection is opened unshared to 959 // eliminate cache contention between the main and background threads. 960 rv = mStorageService->OpenUnsharedDatabase( 961 mCookieFile, mozIStorageService::CONNECTION_DEFAULT, 962 getter_AddRefs(mSyncConn)); 963 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 964 } 965 966 auto guard = MakeScopeExit([&] { mSyncConn = nullptr; }); 967 968 bool tableExists = false; 969 mSyncConn->TableExists("moz_cookies"_ns, &tableExists); 970 if (!tableExists) { 971 rv = CreateTable(); 972 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 973 974 } else { 975 // table already exists; check the schema version before reading 976 int32_t dbSchemaVersion; 977 rv = mSyncConn->GetSchemaVersion(&dbSchemaVersion); 978 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 979 980 // Start a transaction for the whole migration block. 981 mozStorageTransaction transaction(mSyncConn, true); 982 983 // XXX Handle the error, bug 1696130. 984 (void)NS_WARN_IF(NS_FAILED(transaction.Start())); 985 986 switch (dbSchemaVersion) { 987 // Upgrading. 988 // Every time you increment the database schema, you need to implement 989 // the upgrading code from the previous version to the new one. If 990 // migration fails for any reason, it's a bug -- so we return RESULT_RETRY 991 // such that the original database will be saved, in the hopes that we 992 // might one day see it and fix it. 993 case 1: { 994 // Add the lastAccessed column to the table. 995 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 996 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER")); 997 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 998 } 999 // Fall through to the next upgrade. 1000 [[fallthrough]]; 1001 1002 case 2: { 1003 // Add the baseDomain column and index to the table. 1004 rv = mSyncConn->ExecuteSimpleSQL( 1005 "ALTER TABLE moz_cookies ADD baseDomain TEXT"_ns); 1006 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1007 1008 // Compute the baseDomains for the table. This must be done eagerly 1009 // otherwise we won't be able to synchronously read in individual 1010 // domains on demand. 1011 const int64_t SCHEMA2_IDX_ID = 0; 1012 const int64_t SCHEMA2_IDX_HOST = 1; 1013 nsCOMPtr<mozIStorageStatement> select; 1014 rv = mSyncConn->CreateStatement("SELECT id, host FROM moz_cookies"_ns, 1015 getter_AddRefs(select)); 1016 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1017 1018 nsCOMPtr<mozIStorageStatement> update; 1019 rv = mSyncConn->CreateStatement( 1020 nsLiteralCString("UPDATE moz_cookies SET baseDomain = " 1021 ":baseDomain WHERE id = :id"), 1022 getter_AddRefs(update)); 1023 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1024 1025 nsCString baseDomain; 1026 nsCString host; 1027 bool hasResult; 1028 while (true) { 1029 rv = select->ExecuteStep(&hasResult); 1030 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1031 1032 if (!hasResult) { 1033 break; 1034 } 1035 1036 int64_t id = select->AsInt64(SCHEMA2_IDX_ID); 1037 select->GetUTF8String(SCHEMA2_IDX_HOST, host); 1038 1039 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, 1040 baseDomain); 1041 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1042 1043 mozStorageStatementScoper scoper(update); 1044 1045 rv = update->BindUTF8StringByName("baseDomain"_ns, baseDomain); 1046 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1047 rv = update->BindInt64ByName("id"_ns, id); 1048 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1049 1050 rv = update->ExecuteStep(&hasResult); 1051 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1052 } 1053 1054 // Create an index on baseDomain. 1055 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1056 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)")); 1057 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1058 } 1059 // Fall through to the next upgrade. 1060 [[fallthrough]]; 1061 1062 case 3: { 1063 // Add the creationTime column to the table, and create a unique index 1064 // on (name, host, path). Before we do this, we have to purge the table 1065 // of expired cookies such that we know that the (name, host, path) 1066 // index is truly unique -- otherwise we can't create the index. Note 1067 // that we can't just execute a statement to delete all rows where the 1068 // expiry column is in the past -- doing so would rely on the clock 1069 // (both now and when previous cookies were set) being monotonic. 1070 1071 // Select the whole table, and order by the fields we're interested in. 1072 // This means we can simply do a linear traversal of the results and 1073 // check for duplicates as we go. 1074 const int64_t SCHEMA3_IDX_ID = 0; 1075 const int64_t SCHEMA3_IDX_NAME = 1; 1076 const int64_t SCHEMA3_IDX_HOST = 2; 1077 const int64_t SCHEMA3_IDX_PATH = 3; 1078 nsCOMPtr<mozIStorageStatement> select; 1079 rv = mSyncConn->CreateStatement( 1080 nsLiteralCString( 1081 "SELECT id, name, host, path FROM moz_cookies " 1082 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"), 1083 getter_AddRefs(select)); 1084 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1085 1086 nsCOMPtr<mozIStorageStatement> deleteExpired; 1087 rv = mSyncConn->CreateStatement( 1088 "DELETE FROM moz_cookies WHERE id = :id"_ns, 1089 getter_AddRefs(deleteExpired)); 1090 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1091 1092 // Read the first row. 1093 bool hasResult; 1094 rv = select->ExecuteStep(&hasResult); 1095 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1096 1097 if (hasResult) { 1098 nsCString name1; 1099 nsCString host1; 1100 nsCString path1; 1101 int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID); 1102 select->GetUTF8String(SCHEMA3_IDX_NAME, name1); 1103 select->GetUTF8String(SCHEMA3_IDX_HOST, host1); 1104 select->GetUTF8String(SCHEMA3_IDX_PATH, path1); 1105 1106 nsCString name2; 1107 nsCString host2; 1108 nsCString path2; 1109 while (true) { 1110 // Read the second row. 1111 rv = select->ExecuteStep(&hasResult); 1112 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1113 1114 if (!hasResult) { 1115 break; 1116 } 1117 1118 int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID); 1119 select->GetUTF8String(SCHEMA3_IDX_NAME, name2); 1120 select->GetUTF8String(SCHEMA3_IDX_HOST, host2); 1121 select->GetUTF8String(SCHEMA3_IDX_PATH, path2); 1122 1123 // If the two rows match in (name, host, path), we know the earlier 1124 // row has an earlier expiry time. Delete it. 1125 if (name1 == name2 && host1 == host2 && path1 == path2) { 1126 mozStorageStatementScoper scoper(deleteExpired); 1127 1128 rv = deleteExpired->BindInt64ByName("id"_ns, id1); 1129 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1130 1131 rv = deleteExpired->ExecuteStep(&hasResult); 1132 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1133 } 1134 1135 // Make the second row the first for the next iteration. 1136 name1 = name2; 1137 host1 = host2; 1138 path1 = path2; 1139 id1 = id2; 1140 } 1141 } 1142 1143 // Add the creationTime column to the table. 1144 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1145 "ALTER TABLE moz_cookies ADD creationTime INTEGER")); 1146 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1147 1148 // Copy the id of each row into the new creationTime column. 1149 rv = mSyncConn->ExecuteSimpleSQL( 1150 nsLiteralCString("UPDATE moz_cookies SET creationTime = " 1151 "(SELECT id WHERE id = moz_cookies.id)")); 1152 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1153 1154 // Create a unique index on (name, host, path) to allow fast lookup. 1155 rv = mSyncConn->ExecuteSimpleSQL( 1156 nsLiteralCString("CREATE UNIQUE INDEX moz_uniqueid " 1157 "ON moz_cookies (name, host, path)")); 1158 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1159 } 1160 // Fall through to the next upgrade. 1161 [[fallthrough]]; 1162 1163 case 4: { 1164 // We need to add appId/inBrowserElement, plus change a constraint on 1165 // the table (unique entries now include appId/inBrowserElement): 1166 // this requires creating a new table and copying the data to it. We 1167 // then rename the new table to the old name. 1168 // 1169 // Why we made this change: appId/inBrowserElement allow "cookie jars" 1170 // for Firefox OS. We create a separate cookie namespace per {appId, 1171 // inBrowserElement}. When upgrading, we convert existing cookies 1172 // (which imply we're on desktop/mobile) to use {0, false}, as that is 1173 // the only namespace used by a non-Firefox-OS implementation. 1174 1175 // Rename existing table 1176 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1177 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); 1178 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1179 1180 // Drop existing index (CreateTable will create new one for new table) 1181 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns); 1182 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1183 1184 // Create new table (with new fields and new unique constraint) 1185 rv = CreateTableForSchemaVersion5(); 1186 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1187 1188 // Copy data from old table, using appId/inBrowser=0 for existing rows 1189 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1190 "INSERT INTO moz_cookies " 1191 "(baseDomain, appId, inBrowserElement, name, value, host, path, " 1192 "expiry," 1193 " lastAccessed, creationTime, isSecure, isHttpOnly) " 1194 "SELECT baseDomain, 0, 0, name, value, host, path, expiry," 1195 " lastAccessed, creationTime, isSecure, isHttpOnly " 1196 "FROM moz_cookies_old")); 1197 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1198 1199 // Drop old table 1200 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns); 1201 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1202 1203 COOKIE_LOGSTRING(LogLevel::Debug, 1204 ("Upgraded database to schema version 5")); 1205 } 1206 // Fall through to the next upgrade. 1207 [[fallthrough]]; 1208 1209 case 5: { 1210 // Change in the version: Replace the columns |appId| and 1211 // |inBrowserElement| by a single column |originAttributes|. 1212 // 1213 // Why we made this change: FxOS new security model (NSec) encapsulates 1214 // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to 1215 // make it easier to modify the contents of this structure in the 1216 // future. 1217 // 1218 // We do the migration in several steps: 1219 // 1. Rename the old table. 1220 // 2. Create a new table. 1221 // 3. Copy data from the old table to the new table; convert appId and 1222 // inBrowserElement to originAttributes in the meantime. 1223 1224 // Rename existing table. 1225 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1226 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); 1227 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1228 1229 // Drop existing index (CreateTable will create new one for new table). 1230 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns); 1231 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1232 1233 // Create new table with new fields and new unique constraint. 1234 rv = CreateTableForSchemaVersion6(); 1235 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1236 1237 // Copy data from old table without the two deprecated columns appId and 1238 // inBrowserElement. 1239 nsCOMPtr<mozIStorageFunction> convertToOriginAttrs( 1240 new ConvertAppIdToOriginAttrsSQLFunction()); 1241 NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY); 1242 1243 constexpr auto convertToOriginAttrsName = 1244 "CONVERT_TO_ORIGIN_ATTRIBUTES"_ns; 1245 1246 rv = mSyncConn->CreateFunction(convertToOriginAttrsName, 2, 1247 convertToOriginAttrs); 1248 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1249 1250 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1251 "INSERT INTO moz_cookies " 1252 "(baseDomain, originAttributes, name, value, host, path, expiry," 1253 " lastAccessed, creationTime, isSecure, isHttpOnly) " 1254 "SELECT baseDomain, " 1255 " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement)," 1256 " name, value, host, path, expiry, lastAccessed, creationTime, " 1257 " isSecure, isHttpOnly " 1258 "FROM moz_cookies_old")); 1259 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1260 1261 rv = mSyncConn->RemoveFunction(convertToOriginAttrsName); 1262 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1263 1264 // Drop old table 1265 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns); 1266 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1267 1268 COOKIE_LOGSTRING(LogLevel::Debug, 1269 ("Upgraded database to schema version 6")); 1270 } 1271 [[fallthrough]]; 1272 1273 case 6: { 1274 // We made a mistake in schema version 6. We cannot remove expected 1275 // columns of any version (checked in the default case) from cookie 1276 // database, because doing this would destroy the possibility of 1277 // downgrading database. 1278 // 1279 // This version simply restores appId and inBrowserElement columns in 1280 // order to fix downgrading issue even though these two columns are no 1281 // longer used in the latest schema. 1282 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1283 "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;")); 1284 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1285 1286 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1287 "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;")); 1288 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1289 1290 // Compute and populate the values of appId and inBrwoserElement from 1291 // originAttributes. 1292 nsCOMPtr<mozIStorageFunction> setAppId( 1293 new SetAppIdFromOriginAttributesSQLFunction()); 1294 NS_ENSURE_TRUE(setAppId, RESULT_RETRY); 1295 1296 constexpr auto setAppIdName = "SET_APP_ID"_ns; 1297 1298 rv = mSyncConn->CreateFunction(setAppIdName, 1, setAppId); 1299 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1300 1301 nsCOMPtr<mozIStorageFunction> setInBrowser( 1302 new SetInBrowserFromOriginAttributesSQLFunction()); 1303 NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY); 1304 1305 constexpr auto setInBrowserName = "SET_IN_BROWSER"_ns; 1306 1307 rv = mSyncConn->CreateFunction(setInBrowserName, 1, setInBrowser); 1308 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1309 1310 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1311 "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), " 1312 "inBrowserElement = SET_IN_BROWSER(originAttributes);")); 1313 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1314 1315 rv = mSyncConn->RemoveFunction(setAppIdName); 1316 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1317 1318 rv = mSyncConn->RemoveFunction(setInBrowserName); 1319 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1320 1321 COOKIE_LOGSTRING(LogLevel::Debug, 1322 ("Upgraded database to schema version 7")); 1323 } 1324 [[fallthrough]]; 1325 1326 case 7: { 1327 // Remove the appId field from moz_cookies. 1328 // 1329 // Unfortunately sqlite doesn't support dropping columns using ALTER 1330 // TABLE, so we need to go through the procedure documented in 1331 // https://www.sqlite.org/lang_altertable.html. 1332 1333 // Drop existing index 1334 rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns); 1335 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1336 1337 // Create a new_moz_cookies table without the appId field. 1338 rv = mSyncConn->ExecuteSimpleSQL( 1339 nsLiteralCString("CREATE TABLE new_moz_cookies(" 1340 "id INTEGER PRIMARY KEY, " 1341 "baseDomain TEXT, " 1342 "originAttributes TEXT NOT NULL DEFAULT '', " 1343 "name TEXT, " 1344 "value TEXT, " 1345 "host TEXT, " 1346 "path TEXT, " 1347 "expiry INTEGER, " 1348 "lastAccessed INTEGER, " 1349 "creationTime INTEGER, " 1350 "isSecure INTEGER, " 1351 "isHttpOnly INTEGER, " 1352 "inBrowserElement INTEGER DEFAULT 0, " 1353 "CONSTRAINT moz_uniqueid UNIQUE (name, host, " 1354 "path, originAttributes)" 1355 ")")); 1356 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1357 1358 // Move the data over. 1359 rv = mSyncConn->ExecuteSimpleSQL( 1360 nsLiteralCString("INSERT INTO new_moz_cookies (" 1361 "id, " 1362 "baseDomain, " 1363 "originAttributes, " 1364 "name, " 1365 "value, " 1366 "host, " 1367 "path, " 1368 "expiry, " 1369 "lastAccessed, " 1370 "creationTime, " 1371 "isSecure, " 1372 "isHttpOnly, " 1373 "inBrowserElement " 1374 ") SELECT " 1375 "id, " 1376 "baseDomain, " 1377 "originAttributes, " 1378 "name, " 1379 "value, " 1380 "host, " 1381 "path, " 1382 "expiry, " 1383 "lastAccessed, " 1384 "creationTime, " 1385 "isSecure, " 1386 "isHttpOnly, " 1387 "inBrowserElement " 1388 "FROM moz_cookies;")); 1389 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1390 1391 // Drop the old table 1392 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies;"_ns); 1393 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1394 1395 // Rename new_moz_cookies to moz_cookies. 1396 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1397 "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;")); 1398 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1399 1400 // Recreate our index. 1401 rv = mSyncConn->ExecuteSimpleSQL( 1402 nsLiteralCString("CREATE INDEX moz_basedomain ON moz_cookies " 1403 "(baseDomain, originAttributes)")); 1404 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1405 1406 COOKIE_LOGSTRING(LogLevel::Debug, 1407 ("Upgraded database to schema version 8")); 1408 } 1409 [[fallthrough]]; 1410 1411 case 8: { 1412 // Add the sameSite column to the table. 1413 rv = mSyncConn->ExecuteSimpleSQL( 1414 "ALTER TABLE moz_cookies ADD sameSite INTEGER"_ns); 1415 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1416 1417 COOKIE_LOGSTRING(LogLevel::Debug, 1418 ("Upgraded database to schema version 9")); 1419 } 1420 [[fallthrough]]; 1421 1422 case 9: { 1423 // Add the rawSameSite column to the table. 1424 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1425 "ALTER TABLE moz_cookies ADD rawSameSite INTEGER")); 1426 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1427 1428 // Copy the current sameSite value into rawSameSite. 1429 rv = mSyncConn->ExecuteSimpleSQL( 1430 "UPDATE moz_cookies SET rawSameSite = sameSite"_ns); 1431 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1432 1433 COOKIE_LOGSTRING(LogLevel::Debug, 1434 ("Upgraded database to schema version 10")); 1435 } 1436 [[fallthrough]]; 1437 1438 case 10: { 1439 // Rename existing table 1440 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1441 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); 1442 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1443 1444 // Create a new moz_cookies table without the baseDomain field. 1445 rv = mSyncConn->ExecuteSimpleSQL( 1446 nsLiteralCString("CREATE TABLE moz_cookies(" 1447 "id INTEGER PRIMARY KEY, " 1448 "originAttributes TEXT NOT NULL DEFAULT '', " 1449 "name TEXT, " 1450 "value TEXT, " 1451 "host TEXT, " 1452 "path TEXT, " 1453 "expiry INTEGER, " 1454 "lastAccessed INTEGER, " 1455 "creationTime INTEGER, " 1456 "isSecure INTEGER, " 1457 "isHttpOnly INTEGER, " 1458 "inBrowserElement INTEGER DEFAULT 0, " 1459 "sameSite INTEGER DEFAULT 0, " 1460 "rawSameSite INTEGER DEFAULT 0, " 1461 "CONSTRAINT moz_uniqueid UNIQUE (name, host, " 1462 "path, originAttributes)" 1463 ")")); 1464 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1465 1466 // Move the data over. 1467 rv = mSyncConn->ExecuteSimpleSQL( 1468 nsLiteralCString("INSERT INTO moz_cookies (" 1469 "id, " 1470 "originAttributes, " 1471 "name, " 1472 "value, " 1473 "host, " 1474 "path, " 1475 "expiry, " 1476 "lastAccessed, " 1477 "creationTime, " 1478 "isSecure, " 1479 "isHttpOnly, " 1480 "inBrowserElement, " 1481 "sameSite, " 1482 "rawSameSite " 1483 ") SELECT " 1484 "id, " 1485 "originAttributes, " 1486 "name, " 1487 "value, " 1488 "host, " 1489 "path, " 1490 "expiry, " 1491 "lastAccessed, " 1492 "creationTime, " 1493 "isSecure, " 1494 "isHttpOnly, " 1495 "inBrowserElement, " 1496 "sameSite, " 1497 "rawSameSite " 1498 "FROM moz_cookies_old " 1499 "WHERE baseDomain NOTNULL;")); 1500 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1501 1502 // Drop the old table 1503 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old;"_ns); 1504 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1505 1506 // Drop the moz_basedomain index from the database (if it hasn't been 1507 // removed already by removing the table). 1508 rv = mSyncConn->ExecuteSimpleSQL( 1509 "DROP INDEX IF EXISTS moz_basedomain;"_ns); 1510 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1511 1512 COOKIE_LOGSTRING(LogLevel::Debug, 1513 ("Upgraded database to schema version 11")); 1514 } 1515 [[fallthrough]]; 1516 1517 case 11: { 1518 // Add the schemeMap column to the table. 1519 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1520 "ALTER TABLE moz_cookies ADD schemeMap INTEGER DEFAULT 0;")); 1521 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1522 1523 COOKIE_LOGSTRING(LogLevel::Debug, 1524 ("Upgraded database to schema version 12")); 1525 } 1526 [[fallthrough]]; 1527 1528 case 12: { 1529 // Add the isPartitionedAttributeSet column to the table. 1530 rv = mSyncConn->ExecuteSimpleSQL( 1531 nsLiteralCString("ALTER TABLE moz_cookies ADD " 1532 "isPartitionedAttributeSet INTEGER DEFAULT 0;")); 1533 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1534 1535 COOKIE_LOGSTRING(LogLevel::Debug, 1536 ("Upgraded database to schema version 13")); 1537 1538 [[fallthrough]]; 1539 } 1540 1541 case 13: { 1542 rv = mSyncConn->ExecuteSimpleSQL( 1543 nsLiteralCString("UPDATE moz_cookies SET expiry = unixepoch() + " 1544 "34560000 WHERE expiry > unixepoch() + 34560000")); 1545 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1546 1547 [[fallthrough]]; 1548 } 1549 1550 case 14: { 1551 nsCOMPtr<mozIStorageStatement> update; 1552 rv = mSyncConn->CreateStatement( 1553 nsLiteralCString("UPDATE moz_cookies SET sameSite = " 1554 ":unsetValue WHERE sameSite = :laxValue AND " 1555 "rawSameSite = :noneValue"), 1556 getter_AddRefs(update)); 1557 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1558 1559 mozStorageStatementScoper scoper(update); 1560 1561 rv = 1562 update->BindInt32ByName("unsetValue"_ns, nsICookie::SAMESITE_UNSET); 1563 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1564 1565 rv = update->BindInt32ByName("laxValue"_ns, nsICookie::SAMESITE_LAX); 1566 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1567 1568 rv = update->BindInt32ByName("noneValue"_ns, nsICookie::SAMESITE_NONE); 1569 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1570 1571 bool hasResult; 1572 rv = update->ExecuteStep(&hasResult); 1573 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1574 1575 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1576 "ALTER TABLE moz_cookies DROP COLUMN rawSameSite;")); 1577 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1578 1579 [[fallthrough]]; 1580 } 1581 1582 case 15: { 1583 rv = mSyncConn->ExecuteSimpleSQL( 1584 nsLiteralCString("UPDATE moz_cookies SET expiry = expiry * 1000;")); 1585 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1586 1587 [[fallthrough]]; 1588 } 1589 1590 case 16: { 1591 // Add the updateTime column to the table. 1592 rv = mSyncConn->ExecuteSimpleSQL( 1593 nsLiteralCString("ALTER TABLE moz_cookies ADD updateTime INTEGER")); 1594 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1595 1596 // OK so... this is tricky because we have to guess the creationTime 1597 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1598 "UPDATE moz_cookies SET updateTime = CAST(strftime('%s','now') AS " 1599 "INTEGER) * 1000000")); 1600 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1601 1602 // No more upgrades. Update the schema version. 1603 rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); 1604 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1605 1606 [[fallthrough]]; 1607 } 1608 1609 case COOKIES_SCHEMA_VERSION: 1610 break; 1611 1612 case 0: { 1613 NS_WARNING("couldn't get schema version!"); 1614 1615 // the table may be usable; someone might've just clobbered the schema 1616 // version. we can treat this case like a downgrade using the codepath 1617 // below, by verifying the columns we care about are all there. for now, 1618 // re-set the schema version in the db, in case the checks succeed (if 1619 // they don't, we're dropping the table anyway). 1620 rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); 1621 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1622 } 1623 // fall through to downgrade check 1624 [[fallthrough]]; 1625 1626 // downgrading. 1627 // if columns have been added to the table, we can still use the ones we 1628 // understand safely. if columns have been deleted or altered, just 1629 // blow away the table and start from scratch! if you change the way 1630 // a column is interpreted, make sure you also change its name so this 1631 // check will catch it. 1632 default: { 1633 // check if all the expected columns exist 1634 nsCOMPtr<mozIStorageStatement> stmt; 1635 rv = mSyncConn->CreateStatement( 1636 nsLiteralCString("SELECT " 1637 "id, " 1638 "originAttributes, " 1639 "name, " 1640 "value, " 1641 "host, " 1642 "path, " 1643 "expiry, " 1644 "lastAccessed, " 1645 "creationTime, " 1646 "isSecure, " 1647 "isHttpOnly, " 1648 "sameSite, " 1649 "schemeMap, " 1650 "isPartitionedAttributeSet " 1651 "FROM moz_cookies"), 1652 getter_AddRefs(stmt)); 1653 if (NS_SUCCEEDED(rv)) { 1654 break; 1655 } 1656 1657 // our columns aren't there - drop the table! 1658 rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies"_ns); 1659 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1660 1661 rv = CreateTable(); 1662 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1663 } break; 1664 } 1665 } 1666 1667 // if we deleted a corrupt db, don't attempt to import - return now 1668 if (aRecreateDB) { 1669 return RESULT_OK; 1670 } 1671 1672 if (StaticPrefs::network_cookie_CHIPS_enabled() && 1673 StaticPrefs::network_cookie_CHIPS_lastMigrateDatabase() < 1674 StaticPrefs::network_cookie_CHIPS_migrateDatabaseTarget()) { 1675 CookiePersistentStorage::MoveUnpartitionedChipsCookies(); 1676 } 1677 1678 // check whether to import or just read in the db 1679 if (tableExists) { 1680 return Read(); 1681 } 1682 1683 return RESULT_OK; 1684 } 1685 1686 void CookiePersistentStorage::MoveUnpartitionedChipsCookies() { 1687 nsCOMPtr<mozIStorageFunction> fetchPartitionKeyFromOAs( 1688 new FetchPartitionKeyFromOAsSQLFunction()); 1689 NS_ENSURE_TRUE_VOID(fetchPartitionKeyFromOAs); 1690 1691 constexpr auto fetchPartitionKeyFromOAsName = 1692 "FETCH_PARTITIONKEY_FROM_OAS"_ns; 1693 1694 nsresult rv = mSyncConn->CreateFunction(fetchPartitionKeyFromOAsName, 1, 1695 fetchPartitionKeyFromOAs); 1696 NS_ENSURE_SUCCESS_VOID(rv); 1697 1698 nsCOMPtr<mozIStorageFunction> updateOAsWithPartitionHost( 1699 new UpdateOAsWithPartitionHostSQLFunction()); 1700 NS_ENSURE_TRUE_VOID(updateOAsWithPartitionHost); 1701 1702 constexpr auto updateOAsWithPartitionHostName = 1703 "UPDATE_OAS_WITH_PARTITION_HOST"_ns; 1704 1705 rv = mSyncConn->CreateFunction(updateOAsWithPartitionHostName, 2, 1706 updateOAsWithPartitionHost); 1707 NS_ENSURE_SUCCESS_VOID(rv); 1708 1709 // Move all cookies with the Partitioned attribute set into their first-party 1710 // partitioned storage by updating the origin attributes. Overwrite any 1711 // existing cookies that may already be there. 1712 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 1713 "UPDATE OR REPLACE moz_cookies " 1714 "SET originAttributes = UPDATE_OAS_WITH_PARTITION_HOST(originAttributes, " 1715 "host) " 1716 "WHERE FETCH_PARTITIONKEY_FROM_OAS(originAttributes) = '' " 1717 "AND isPartitionedAttributeSet = 1;")); 1718 NS_ENSURE_SUCCESS_VOID(rv); 1719 1720 rv = mSyncConn->RemoveFunction(fetchPartitionKeyFromOAsName); 1721 NS_ENSURE_SUCCESS_VOID(rv); 1722 1723 rv = mSyncConn->RemoveFunction(updateOAsWithPartitionHostName); 1724 NS_ENSURE_SUCCESS_VOID(rv); 1725 } 1726 1727 void CookiePersistentStorage::RebuildCorruptDB() { 1728 NS_ASSERTION(!mDBConn, "shouldn't have an open db connection"); 1729 NS_ASSERTION(mCorruptFlag == CookiePersistentStorage::CLOSING_FOR_REBUILD, 1730 "should be in CLOSING_FOR_REBUILD state"); 1731 1732 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1733 1734 mCorruptFlag = CookiePersistentStorage::REBUILDING; 1735 1736 COOKIE_LOGSTRING(LogLevel::Debug, 1737 ("RebuildCorruptDB(): creating new database")); 1738 1739 RefPtr<CookiePersistentStorage> self = this; 1740 nsCOMPtr<nsIRunnable> runnable = 1741 NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [self] { 1742 // The database has been closed, and we're ready to rebuild. Open a 1743 // connection. 1744 OpenDBResult result = self->TryInitDB(true); 1745 1746 nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction( 1747 "RebuildCorruptDB.TryInitDBComplete", [self, result] { 1748 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1749 if (result != RESULT_OK) { 1750 // We're done. Reset our DB connection and statements, and 1751 // notify of closure. 1752 COOKIE_LOGSTRING( 1753 LogLevel::Warning, 1754 ("RebuildCorruptDB(): TryInitDB() failed with result %u", 1755 result)); 1756 self->CleanupCachedStatements(); 1757 self->CleanupDBConnection(); 1758 self->mCorruptFlag = CookiePersistentStorage::OK; 1759 if (os) { 1760 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); 1761 } 1762 return; 1763 } 1764 1765 // Notify observers that we're beginning the rebuild. 1766 if (os) { 1767 os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr); 1768 } 1769 1770 self->InitDBConnInternal(); 1771 1772 // Enumerate the hash, and add cookies to the params array. 1773 mozIStorageAsyncStatement* stmt = self->mStmtInsert; 1774 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; 1775 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); 1776 for (auto iter = self->mHostTable.Iter(); !iter.Done(); 1777 iter.Next()) { 1778 CookieEntry* entry = iter.Get(); 1779 1780 const CookieEntry::ArrayType& cookies = entry->GetCookies(); 1781 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 1782 Cookie* cookie = cookies[i]; 1783 1784 if (!cookie->IsSession()) { 1785 BindCookieParameters(paramsArray, CookieKey(entry), cookie); 1786 } 1787 } 1788 } 1789 1790 // Make sure we've got something to write. If we don't, we're 1791 // done. 1792 uint32_t length; 1793 paramsArray->GetLength(&length); 1794 if (length == 0) { 1795 COOKIE_LOGSTRING( 1796 LogLevel::Debug, 1797 ("RebuildCorruptDB(): nothing to write, rebuild complete")); 1798 self->mCorruptFlag = CookiePersistentStorage::OK; 1799 return; 1800 } 1801 1802 self->MaybeStoreCookiesToDB(paramsArray); 1803 }); 1804 NS_DispatchToMainThread(innerRunnable); 1805 }); 1806 mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1807 } 1808 1809 void CookiePersistentStorage::HandleDBClosed() { 1810 COOKIE_LOGSTRING(LogLevel::Debug, 1811 ("HandleDBClosed(): CookieStorage %p closed", this)); 1812 1813 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1814 1815 switch (mCorruptFlag) { 1816 case CookiePersistentStorage::OK: { 1817 // Database is healthy. Notify of closure. 1818 if (os) { 1819 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); 1820 } 1821 break; 1822 } 1823 case CookiePersistentStorage::CLOSING_FOR_REBUILD: { 1824 // Our close finished. Start the rebuild, and notify of db closure later. 1825 RebuildCorruptDB(); 1826 break; 1827 } 1828 case CookiePersistentStorage::REBUILDING: { 1829 // We encountered an error during rebuild, closed the database, and now 1830 // here we are. We already have a 'cookies.sqlite.bak' from the original 1831 // dead database; we don't want to overwrite it, so let's move this one to 1832 // 'cookies.sqlite.bak-rebuild'. 1833 nsCOMPtr<nsIFile> backupFile; 1834 mCookieFile->Clone(getter_AddRefs(backupFile)); 1835 nsresult rv = backupFile->MoveToNative( 1836 nullptr, nsLiteralCString(COOKIES_FILE ".bak-rebuild")); 1837 1838 COOKIE_LOGSTRING(LogLevel::Warning, 1839 ("HandleDBClosed(): CookieStorage %p encountered error " 1840 "rebuilding db; move to " 1841 "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32, 1842 this, static_cast<uint32_t>(rv))); 1843 if (os) { 1844 os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); 1845 } 1846 break; 1847 } 1848 } 1849 } 1850 1851 CookiePersistentStorage::OpenDBResult CookiePersistentStorage::Read() { 1852 MOZ_ASSERT(NS_GetCurrentThread() == mThread); 1853 1854 // Read in the data synchronously. 1855 // see IDX_NAME, etc. for parameter indexes 1856 nsCOMPtr<mozIStorageStatement> stmt; 1857 nsresult rv = 1858 mSyncConn->CreateStatement(nsLiteralCString("SELECT " 1859 "name, " 1860 "value, " 1861 "host, " 1862 "path, " 1863 "expiry, " 1864 "lastAccessed, " 1865 "creationTime, " 1866 "isSecure, " 1867 "isHttpOnly, " 1868 "originAttributes, " 1869 "sameSite, " 1870 "schemeMap, " 1871 "isPartitionedAttributeSet, " 1872 "updateTime " 1873 "FROM moz_cookies"), 1874 getter_AddRefs(stmt)); 1875 1876 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); 1877 1878 if (NS_WARN_IF(!mReadArray.IsEmpty())) { 1879 mReadArray.Clear(); 1880 } 1881 mReadArray.SetCapacity(kMaxNumberOfCookies); 1882 1883 nsCString baseDomain; 1884 nsCString name; 1885 nsCString value; 1886 nsCString host; 1887 nsCString path; 1888 bool hasResult; 1889 while (true) { 1890 rv = stmt->ExecuteStep(&hasResult); 1891 if (NS_WARN_IF(NS_FAILED(rv))) { 1892 mReadArray.Clear(); 1893 return RESULT_RETRY; 1894 } 1895 1896 if (!hasResult) { 1897 break; 1898 } 1899 1900 stmt->GetUTF8String(IDX_HOST, host); 1901 1902 rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); 1903 if (NS_FAILED(rv)) { 1904 COOKIE_LOGSTRING(LogLevel::Debug, 1905 ("Read(): Ignoring invalid host '%s'", host.get())); 1906 continue; 1907 } 1908 1909 nsAutoCString suffix; 1910 OriginAttributes attrs; 1911 stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix); 1912 // If PopulateFromSuffix failed we just ignore the OA attributes 1913 // that we don't support 1914 (void)attrs.PopulateFromSuffix(suffix); 1915 1916 CookieKey key(baseDomain, attrs); 1917 CookieDomainTuple* tuple = mReadArray.AppendElement(); 1918 tuple->key = std::move(key); 1919 tuple->originAttributes = attrs; 1920 tuple->cookie = GetCookieFromRow(stmt); 1921 } 1922 1923 COOKIE_LOGSTRING(LogLevel::Debug, 1924 ("Read(): %zu cookies read", mReadArray.Length())); 1925 1926 return RESULT_OK; 1927 } 1928 1929 // Extract data from a single result row and create an Cookie. 1930 UniquePtr<CookieStruct> CookiePersistentStorage::GetCookieFromRow( 1931 mozIStorageStatement* aRow) { 1932 nsCString name; 1933 nsCString value; 1934 nsCString host; 1935 nsCString path; 1936 DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name); 1937 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1938 rv = aRow->GetUTF8String(IDX_VALUE, value); 1939 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1940 rv = aRow->GetUTF8String(IDX_HOST, host); 1941 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1942 rv = aRow->GetUTF8String(IDX_PATH, path); 1943 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1944 1945 int64_t expiryInMSec = aRow->AsInt64(IDX_EXPIRY_INMSEC); 1946 int64_t lastAccessedInUSec = aRow->AsInt64(IDX_LAST_ACCESSED_INUSEC); 1947 int64_t creationTimeInUSec = aRow->AsInt64(IDX_CREATION_TIME_INUSEC); 1948 int64_t updateTimeInUSec = aRow->AsInt64(IDX_UPDATE_TIME_INUSEC); 1949 bool isSecure = 0 != aRow->AsInt32(IDX_SECURE); 1950 bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY); 1951 int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE); 1952 int32_t schemeMap = aRow->AsInt32(IDX_SCHEME_MAP); 1953 bool isPartitionedAttributeSet = 1954 0 != aRow->AsInt32(IDX_PARTITIONED_ATTRIBUTE_SET); 1955 1956 // Create a new constCookie and assign the data. 1957 return MakeUnique<CookieStruct>( 1958 name, value, host, path, expiryInMSec, lastAccessedInUSec, 1959 creationTimeInUSec, updateTimeInUSec, isHttpOnly, false, isSecure, 1960 isPartitionedAttributeSet, sameSite, 1961 static_cast<nsICookie::schemeType>(schemeMap)); 1962 } 1963 1964 void CookiePersistentStorage::EnsureInitialized() { 1965 MOZ_ASSERT(NS_IsMainThread()); 1966 1967 bool isAccumulated = false; 1968 1969 if (!mInitialized) { 1970 #ifndef ANDROID 1971 TimeStamp startBlockTime = TimeStamp::Now(); 1972 #endif 1973 MonitorAutoLock lock(mMonitor); 1974 1975 while (!mInitialized) { 1976 mMonitor.Wait(); 1977 } 1978 #ifndef ANDROID 1979 TimeStamp endBlockTime = TimeStamp::Now(); 1980 mozilla::glean::networking::sqlite_cookies_block_main_thread 1981 .AccumulateRawDuration(endBlockTime - startBlockTime); 1982 mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread 1983 .AccumulateRawDuration(TimeDuration::Zero()); 1984 #endif 1985 isAccumulated = true; 1986 } else if (!mEndInitDBConn.IsNull()) { 1987 // We didn't block main thread, and here comes the first cookie request. 1988 // Collect how close we're going to block main thread. 1989 #ifndef ANDROID 1990 TimeStamp now = TimeStamp::Now(); 1991 mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread 1992 .AccumulateRawDuration(now - mEndInitDBConn); 1993 #endif 1994 // Nullify the timestamp so wo don't accumulate this telemetry probe again. 1995 mEndInitDBConn = TimeStamp(); 1996 isAccumulated = true; 1997 } else if (!mInitializedDBConn) { 1998 // A request comes while we finished cookie thread task and InitDBConn is 1999 // on the way from cookie thread to main thread. We're very close to block 2000 // main thread. 2001 #ifndef ANDROID 2002 mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread 2003 .AccumulateRawDuration(TimeDuration::Zero()); 2004 #endif 2005 isAccumulated = true; 2006 } 2007 2008 if (!mInitializedDBConn) { 2009 InitDBConn(); 2010 if (isAccumulated) { 2011 // Nullify the timestamp so wo don't accumulate this telemetry probe 2012 // again. 2013 mEndInitDBConn = TimeStamp(); 2014 } 2015 } 2016 } 2017 2018 void CookiePersistentStorage::InitDBConn() { 2019 MOZ_ASSERT(NS_IsMainThread()); 2020 2021 // We should skip InitDBConn if we close profile during initializing 2022 // CookieStorages and then InitDBConn is called after we close the 2023 // CookieStorages. 2024 if (!mInitialized || mInitializedDBConn) { 2025 return; 2026 } 2027 2028 nsCOMPtr<nsIURI> dummyUri; 2029 nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://example.com"); 2030 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2031 nsTArray<RefPtr<Cookie>> cleanupCookies; 2032 2033 for (uint32_t i = 0; i < mReadArray.Length(); ++i) { 2034 CookieDomainTuple& tuple = mReadArray[i]; 2035 MOZ_ASSERT(!tuple.cookie->isSession()); 2036 2037 // filter invalid non-ipv4 host ending in number from old db values 2038 nsCOMPtr<nsIURIMutator> outMut; 2039 nsCOMPtr<nsIURIMutator> dummyMut; 2040 rv = dummyUri->Mutate(getter_AddRefs(dummyMut)); 2041 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2042 rv = dummyMut->SetHost(tuple.cookie->host(), getter_AddRefs(outMut)); 2043 2044 if (NS_FAILED(rv)) { 2045 COOKIE_LOGSTRING(LogLevel::Debug, ("Removing cookie from db with " 2046 "newly invalid hostname: '%s'", 2047 tuple.cookie->host().get())); 2048 RefPtr<Cookie> cookie = 2049 Cookie::Create(*tuple.cookie, tuple.originAttributes); 2050 cleanupCookies.AppendElement(cookie); 2051 continue; 2052 } 2053 2054 // CreateValidated fixes up the creation and lastAccessed times. 2055 // If the DB is corrupted and the timestaps are far away in the future 2056 // we don't want the creation timestamp to update gLastCreationTimeInUSec 2057 // as that would contaminate all the next creation times. 2058 // We fix up these dates to not be later than the current time. 2059 // The downside is that if the user sets the date far away in the past 2060 // then back to the current date, those cookies will be stale, 2061 // but if we don't fix their dates, those cookies might never be 2062 // evicted. 2063 RefPtr<Cookie> cookie = 2064 Cookie::CreateValidated(*tuple.cookie, tuple.originAttributes); 2065 2066 // Clean up the invalid first-party partitioned cookies that don't have 2067 // the 'partitioned' cookie attribution. This will also ensure that we don't 2068 // read the cookie into memory. 2069 if (CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS( 2070 cookie, tuple.key.mBaseDomain, tuple.key.mOriginAttributes)) { 2071 // We cannot directly use the cookie after validation because the 2072 // timestamps could be different from the cookies in DB. So, we need to 2073 // create one from the cookie struct. 2074 RefPtr<Cookie> invalidCookie = 2075 Cookie::Create(*tuple.cookie, tuple.originAttributes); 2076 cleanupCookies.AppendElement(invalidCookie); 2077 continue; 2078 } 2079 2080 AddCookieToList(tuple.key.mBaseDomain, tuple.key.mOriginAttributes, cookie); 2081 } 2082 2083 if (NS_FAILED(InitDBConnInternal())) { 2084 COOKIE_LOGSTRING(LogLevel::Warning, 2085 ("InitDBConn(): retrying InitDBConnInternal()")); 2086 CleanupCachedStatements(); 2087 CleanupDBConnection(); 2088 if (NS_FAILED(InitDBConnInternal())) { 2089 COOKIE_LOGSTRING( 2090 LogLevel::Warning, 2091 ("InitDBConn(): InitDBConnInternal() failed, closing connection")); 2092 2093 // Game over, clean the connections. 2094 CleanupCachedStatements(); 2095 CleanupDBConnection(); 2096 } 2097 } 2098 mInitializedDBConn = true; 2099 2100 COOKIE_LOGSTRING(LogLevel::Debug, 2101 ("InitDBConn(): mInitializedDBConn = true")); 2102 mEndInitDBConn = TimeStamp::Now(); 2103 2104 for (const auto& cookie : cleanupCookies) { 2105 RemoveCookieFromDB(*cookie); 2106 } 2107 2108 // We will have migrated CHIPS cookies if the pref is set, and .unset it 2109 // to prevent duplicated work. This has to happen in the main thread though, 2110 // so we waited to this point. 2111 if (StaticPrefs::network_cookie_CHIPS_enabled()) { 2112 Preferences::SetUint( 2113 "network.cookie.CHIPS.lastMigrateDatabase", 2114 StaticPrefs::network_cookie_CHIPS_migrateDatabaseTarget()); 2115 } 2116 2117 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 2118 if (os) { 2119 os->NotifyObservers(nullptr, "cookie-db-read", nullptr); 2120 mReadArray.Clear(); 2121 } 2122 2123 // Let's count the valid/invalid cookies when in idle. 2124 nsCOMPtr<nsIRunnable> idleRunnable = NS_NewRunnableFunction( 2125 "CookiePersistentStorage::RecordValidationTelemetry", 2126 [self = RefPtr{this}]() { self->RecordValidationTelemetry(); }); 2127 (void)NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable), 2128 EventQueuePriority::Idle); 2129 } 2130 2131 nsresult CookiePersistentStorage::InitDBConnInternal() { 2132 MOZ_ASSERT(NS_IsMainThread()); 2133 2134 nsresult rv = mStorageService->OpenUnsharedDatabase( 2135 mCookieFile, mozIStorageService::CONNECTION_DEFAULT, 2136 getter_AddRefs(mDBConn)); 2137 NS_ENSURE_SUCCESS(rv, rv); 2138 2139 // Set up our listeners. 2140 mInsertListener = new InsertCookieDBListener(this); 2141 mUpdateListener = new UpdateCookieDBListener(this); 2142 mRemoveListener = new RemoveCookieDBListener(this); 2143 mCloseListener = new CloseCookieDBListener(this); 2144 2145 // Grow cookie db in 512KB increments 2146 mDBConn->SetGrowthIncrement(512 * 1024, ""_ns); 2147 2148 // make operations on the table asynchronous, for performance 2149 mDBConn->ExecuteSimpleSQL("PRAGMA synchronous = OFF"_ns); 2150 2151 // Use write-ahead-logging for performance. We cap the autocheckpoint limit at 2152 // 16 pages (around 500KB). 2153 mDBConn->ExecuteSimpleSQL(nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR 2154 "PRAGMA journal_mode = WAL")); 2155 mDBConn->ExecuteSimpleSQL("PRAGMA wal_autocheckpoint = 16"_ns); 2156 2157 // cache frequently used statements (for insertion, deletion, and updating) 2158 rv = mDBConn->CreateAsyncStatement( 2159 nsLiteralCString("INSERT INTO moz_cookies (" 2160 "originAttributes, " 2161 "name, " 2162 "value, " 2163 "host, " 2164 "path, " 2165 "expiry, " 2166 "lastAccessed, " 2167 "creationTime, " 2168 "isSecure, " 2169 "isHttpOnly, " 2170 "sameSite, " 2171 "schemeMap, " 2172 "isPartitionedAttributeSet, " 2173 "updateTime " 2174 ") VALUES (" 2175 ":originAttributes, " 2176 ":name, " 2177 ":value, " 2178 ":host, " 2179 ":path, " 2180 ":expiry, " 2181 ":lastAccessed, " 2182 ":creationTime, " 2183 ":isSecure, " 2184 ":isHttpOnly, " 2185 ":sameSite, " 2186 ":schemeMap, " 2187 ":isPartitionedAttributeSet, " 2188 ":updateTime " 2189 ")"), 2190 getter_AddRefs(mStmtInsert)); 2191 NS_ENSURE_SUCCESS(rv, rv); 2192 2193 rv = mDBConn->CreateAsyncStatement( 2194 nsLiteralCString("DELETE FROM moz_cookies " 2195 "WHERE name = :name AND host = :host AND path = :path " 2196 "AND originAttributes = :originAttributes"), 2197 getter_AddRefs(mStmtDelete)); 2198 NS_ENSURE_SUCCESS(rv, rv); 2199 2200 rv = mDBConn->CreateAsyncStatement( 2201 nsLiteralCString("UPDATE moz_cookies SET lastAccessed = :lastAccessed " 2202 "WHERE name = :name AND host = :host AND path = :path " 2203 "AND originAttributes = :originAttributes"), 2204 getter_AddRefs(mStmtUpdate)); 2205 return rv; 2206 } 2207 2208 // Sets the schema version and creates the moz_cookies table. 2209 nsresult CookiePersistentStorage::CreateTableWorker(const char* aName) { 2210 // Create the table. 2211 // We default originAttributes to empty string: this is so if users revert to 2212 // an older Firefox version that doesn't know about this field, any cookies 2213 // set will still work once they upgrade back. 2214 nsAutoCString command("CREATE TABLE "); 2215 command.Append(aName); 2216 command.AppendLiteral( 2217 " (" 2218 "id INTEGER PRIMARY KEY, " 2219 "originAttributes TEXT NOT NULL DEFAULT '', " 2220 "name TEXT, " 2221 "value TEXT, " 2222 "host TEXT, " 2223 "path TEXT, " 2224 "expiry INTEGER, " 2225 "lastAccessed INTEGER, " 2226 "creationTime INTEGER, " 2227 "isSecure INTEGER, " 2228 "isHttpOnly INTEGER, " 2229 "inBrowserElement INTEGER DEFAULT 0, " 2230 "sameSite INTEGER DEFAULT 0, " 2231 "schemeMap INTEGER DEFAULT 0, " 2232 "isPartitionedAttributeSet INTEGER DEFAULT 0, " 2233 "updateTime INTEGER, " 2234 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" 2235 ")"); 2236 return mSyncConn->ExecuteSimpleSQL(command); 2237 } 2238 2239 // Sets the schema version and creates the moz_cookies table. 2240 nsresult CookiePersistentStorage::CreateTable() { 2241 // Set the schema version, before creating the table. 2242 nsresult rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); 2243 if (NS_FAILED(rv)) { 2244 return rv; 2245 } 2246 2247 rv = CreateTableWorker("moz_cookies"); 2248 if (NS_FAILED(rv)) { 2249 return rv; 2250 } 2251 2252 return NS_OK; 2253 } 2254 2255 // Sets the schema version and creates the moz_cookies table. 2256 nsresult CookiePersistentStorage::CreateTableForSchemaVersion6() { 2257 // Set the schema version, before creating the table. 2258 nsresult rv = mSyncConn->SetSchemaVersion(6); 2259 if (NS_FAILED(rv)) { 2260 return rv; 2261 } 2262 2263 // Create the table. 2264 // We default originAttributes to empty string: this is so if users revert to 2265 // an older Firefox version that doesn't know about this field, any cookies 2266 // set will still work once they upgrade back. 2267 rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 2268 "CREATE TABLE moz_cookies (" 2269 "id INTEGER PRIMARY KEY, " 2270 "baseDomain TEXT, " 2271 "originAttributes TEXT NOT NULL DEFAULT '', " 2272 "name TEXT, " 2273 "value TEXT, " 2274 "host TEXT, " 2275 "path TEXT, " 2276 "expiry INTEGER, " 2277 "lastAccessed INTEGER, " 2278 "creationTime INTEGER, " 2279 "isSecure INTEGER, " 2280 "isHttpOnly INTEGER, " 2281 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" 2282 ")")); 2283 if (NS_FAILED(rv)) { 2284 return rv; 2285 } 2286 2287 // Create an index on baseDomain. 2288 return mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 2289 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " 2290 "originAttributes)")); 2291 } 2292 2293 // Sets the schema version and creates the moz_cookies table. 2294 nsresult CookiePersistentStorage::CreateTableForSchemaVersion5() { 2295 // Set the schema version, before creating the table. 2296 nsresult rv = mSyncConn->SetSchemaVersion(5); 2297 if (NS_FAILED(rv)) { 2298 return rv; 2299 } 2300 2301 // Create the table. We default appId/inBrowserElement to 0: this is so if 2302 // users revert to an older Firefox version that doesn't know about these 2303 // fields, any cookies set will still work once they upgrade back. 2304 rv = mSyncConn->ExecuteSimpleSQL( 2305 nsLiteralCString("CREATE TABLE moz_cookies (" 2306 "id INTEGER PRIMARY KEY, " 2307 "baseDomain TEXT, " 2308 "appId INTEGER DEFAULT 0, " 2309 "inBrowserElement INTEGER DEFAULT 0, " 2310 "name TEXT, " 2311 "value TEXT, " 2312 "host TEXT, " 2313 "path TEXT, " 2314 "expiry INTEGER, " 2315 "lastAccessed INTEGER, " 2316 "creationTime INTEGER, " 2317 "isSecure INTEGER, " 2318 "isHttpOnly INTEGER, " 2319 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, " 2320 "appId, inBrowserElement)" 2321 ")")); 2322 if (NS_FAILED(rv)) { 2323 return rv; 2324 } 2325 2326 // Create an index on baseDomain. 2327 return mSyncConn->ExecuteSimpleSQL(nsLiteralCString( 2328 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " 2329 "appId, " 2330 "inBrowserElement)")); 2331 } 2332 2333 nsresult CookiePersistentStorage::RunInTransaction( 2334 nsICookieTransactionCallback* aCallback) { 2335 if (NS_WARN_IF(!mDBConn)) { 2336 return NS_ERROR_NOT_AVAILABLE; 2337 } 2338 2339 mozStorageTransaction transaction(mDBConn, true); 2340 2341 // XXX Handle the error, bug 1696130. 2342 (void)NS_WARN_IF(NS_FAILED(transaction.Start())); 2343 2344 if (NS_FAILED(aCallback->Callback())) { 2345 (void)transaction.Rollback(); 2346 return NS_ERROR_FAILURE; 2347 } 2348 2349 return NS_OK; 2350 } 2351 2352 // purges expired and old cookies in a batch operation. 2353 already_AddRefed<nsIArray> CookiePersistentStorage::PurgeCookies( 2354 int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies, 2355 int64_t aCookiePurgeAge) { 2356 // Create a params array to batch the removals. This is OK here because 2357 // all the removals are in order, and there are no interleaved additions. 2358 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; 2359 if (mDBConn) { 2360 mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray)); 2361 } 2362 2363 RefPtr<CookiePersistentStorage> self = this; 2364 2365 return PurgeCookiesWithCallbacks( 2366 aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge, 2367 [paramsArray, self](const CookieListIter& aIter) { 2368 self->PrepareCookieRemoval(*aIter.Cookie(), paramsArray); 2369 self->RemoveCookieFromListInternal(aIter); 2370 }, 2371 [paramsArray, self]() { 2372 if (paramsArray) { 2373 self->DeleteFromDB(paramsArray); 2374 } 2375 }); 2376 } 2377 2378 void CookiePersistentStorage::CollectCookieJarSizeData() { 2379 COOKIE_LOGSTRING(LogLevel::Debug, 2380 ("CookiePersistentStorage::CollectCookieJarSizeData")); 2381 2382 uint32_t sumPartitioned = 0; 2383 uint32_t sumUnpartitioned = 0; 2384 for (const auto& cookieEntry : mHostTable) { 2385 if (cookieEntry.IsPartitioned()) { 2386 uint16_t cePartitioned = cookieEntry.GetCookies().Length(); 2387 sumPartitioned += cePartitioned; 2388 mozilla::glean::networking::cookie_count_part_by_key 2389 .AccumulateSingleSample(cePartitioned); 2390 } else { 2391 uint16_t ceUnpartitioned = cookieEntry.GetCookies().Length(); 2392 sumUnpartitioned += ceUnpartitioned; 2393 mozilla::glean::networking::cookie_count_unpart_by_key 2394 .AccumulateSingleSample(ceUnpartitioned); 2395 } 2396 } 2397 2398 mozilla::glean::networking::cookie_count_total.AccumulateSingleSample( 2399 mCookieCount); 2400 mozilla::glean::networking::cookie_count_partitioned.AccumulateSingleSample( 2401 sumPartitioned); 2402 mozilla::glean::networking::cookie_count_unpartitioned.AccumulateSingleSample( 2403 sumUnpartitioned); 2404 } 2405 2406 void CookiePersistentStorage::RecordValidationTelemetry() { 2407 MOZ_ASSERT(NS_IsMainThread()); 2408 2409 RefPtr<CookieService> cs = CookieService::GetSingleton(); 2410 if (!cs) { 2411 // We are shutting down, or something bad is happening. 2412 return; 2413 } 2414 2415 struct CookieToAddOrRemove { 2416 nsCString mBaseDomain; 2417 OriginAttributes mOriginAttributes; 2418 RefPtr<Cookie> mCookie; 2419 }; 2420 2421 nsTArray<CookieToAddOrRemove> listToAdd; 2422 nsTArray<CookieToAddOrRemove> listToRemove; 2423 2424 for (const auto& entry : mHostTable) { 2425 const CookieEntry::ArrayType& cookies = entry.GetCookies(); 2426 for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { 2427 Cookie* cookie = cookies[i]; 2428 2429 RefPtr<CookieValidation> validation = 2430 CookieValidation::Validate(cookie->ToIPC()); 2431 mozilla::glean::networking::cookie_db_validation 2432 .Get(ValidationErrorToLabel(validation->Result())) 2433 .Add(1); 2434 2435 // We are unable to recover from all the possible errors. Let's fix the 2436 // most common ones. 2437 switch (validation->Result()) { 2438 case nsICookieValidation::eRejectedNoneRequiresSecure: { 2439 RefPtr<Cookie> newCookie = 2440 Cookie::Create(cookie->ToIPC(), entry.mOriginAttributes); 2441 MOZ_ASSERT(newCookie); 2442 2443 newCookie->SetSameSite(nsICookie::SAMESITE_UNSET); 2444 newCookie->SetCreationTimeInUSec(cookie->CreationTimeInUSec()); 2445 2446 listToAdd.AppendElement(CookieToAddOrRemove{ 2447 entry.mBaseDomain, entry.mOriginAttributes, newCookie}); 2448 break; 2449 } 2450 2451 case nsICookieValidation::eRejectedAttributeExpiryOversize: { 2452 RefPtr<Cookie> newCookie = 2453 Cookie::Create(cookie->ToIPC(), entry.mOriginAttributes); 2454 MOZ_ASSERT(newCookie); 2455 2456 int64_t currentTimeInMSec = PR_Now() / PR_USEC_PER_MSEC; 2457 2458 newCookie->SetExpiryInMSec(CookieCommons::MaybeCapExpiry( 2459 currentTimeInMSec, cookie->ExpiryInMSec())); 2460 newCookie->SetCreationTimeInUSec(cookie->CreationTimeInUSec()); 2461 2462 listToAdd.AppendElement(CookieToAddOrRemove{ 2463 entry.mBaseDomain, entry.mOriginAttributes, newCookie}); 2464 break; 2465 } 2466 2467 case nsICookieValidation::eRejectedEmptyNameAndValue: 2468 [[fallthrough]]; 2469 case nsICookieValidation::eRejectedInvalidCharName: 2470 [[fallthrough]]; 2471 case nsICookieValidation::eRejectedInvalidCharValue: 2472 listToRemove.AppendElement(CookieToAddOrRemove{ 2473 entry.mBaseDomain, entry.mOriginAttributes, cookie}); 2474 break; 2475 2476 default: 2477 // Nothing to do here. 2478 break; 2479 } 2480 } 2481 } 2482 2483 for (CookieToAddOrRemove& data : listToAdd) { 2484 AddCookie(nullptr, data.mBaseDomain, data.mOriginAttributes, data.mCookie, 2485 data.mCookie->CreationTimeInUSec(), nullptr, VoidCString(), true, 2486 !data.mOriginAttributes.mPartitionKey.IsEmpty(), nullptr, 2487 nullptr); 2488 } 2489 2490 for (CookieToAddOrRemove& data : listToRemove) { 2491 RemoveCookie(data.mBaseDomain, data.mOriginAttributes, data.mCookie->Host(), 2492 data.mCookie->Name(), data.mCookie->Path(), 2493 /* is http: */ true, nullptr); 2494 } 2495 2496 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 2497 if (os) { 2498 os->NotifyObservers(nullptr, "cookies-validated", nullptr); 2499 } 2500 } 2501 2502 } // namespace net 2503 } // namespace mozilla