tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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