tor-browser

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

DBSchema.cpp (126728B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/cache/DBSchema.h"
      8 
      9 #include "ipc/IPCMessageUtils.h"
     10 #include "mozIStorageConnection.h"
     11 #include "mozIStorageFunction.h"
     12 #include "mozIStorageStatement.h"
     13 #include "mozStorageHelper.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/ResultExtensions.h"
     16 #include "mozilla/StaticPrefs_extensions.h"
     17 #include "mozilla/dom/HeadersBinding.h"
     18 #include "mozilla/dom/InternalHeaders.h"
     19 #include "mozilla/dom/InternalResponse.h"
     20 #include "mozilla/dom/RequestBinding.h"
     21 #include "mozilla/dom/ResponseBinding.h"
     22 #include "mozilla/dom/cache/CacheCommon.h"
     23 #include "mozilla/dom/cache/CacheTypes.h"
     24 #include "mozilla/dom/cache/FileUtils.h"
     25 #include "mozilla/dom/cache/SavedTypes.h"
     26 #include "mozilla/dom/cache/TypeUtils.h"
     27 #include "mozilla/dom/cache/Types.h"
     28 #include "mozilla/dom/quota/ResultExtensions.h"
     29 #include "mozilla/psm/TransportSecurityInfo.h"
     30 #include "mozilla/storage/Variant.h"
     31 #include "nsCOMPtr.h"
     32 #include "nsCharSeparatedTokenizer.h"
     33 #include "nsComponentManagerUtils.h"
     34 #include "nsHttp.h"
     35 #include "nsIContentPolicy.h"
     36 #include "nsICryptoHash.h"
     37 #include "nsIURI.h"
     38 #include "nsNetCID.h"
     39 #include "nsPrintfCString.h"
     40 #include "nsTArray.h"
     41 
     42 namespace mozilla::dom::cache::db {
     43 const int32_t kFirstShippedSchemaVersion = 15;
     44 namespace {
     45 // ## Firefox 57 Cache API v25/v26/v27 Schema Hack Info
     46 // ### Overview
     47 // In Firefox 57 we introduced Cache API schema version 26 and Quota Manager
     48 // schema v3 to support tracking padding for opaque responses.  Unfortunately,
     49 // Firefox 57 is a big release that may potentially result in users downgrading
     50 // to Firefox 56 due to 57 retiring add-ons.  These schema changes have the
     51 // unfortunate side-effect of causing QuotaManager and all its clients to break
     52 // if the user downgrades to 56.  In order to avoid making a bad situation
     53 // worse, we're now retrofitting 57 so that Firefox 56 won't freak out.
     54 //
     55 // ### Implementation
     56 // We're introducing a new schema version 27 that uses an on-disk schema version
     57 // of v25.  We differentiate v25 from v27 by the presence of the column added
     58 // by v26.  This translates to:
     59 // - v25: on-disk schema=25, no "response_padding_size" column in table
     60 //   "entries".
     61 // - v26: on-disk schema=26, yes "response_padding_size" column in table
     62 //   "entries".
     63 // - v27: on-disk schema=25, yes "response_padding_size" column in table
     64 //   "entries".
     65 //
     66 // ### Fallout
     67 // Firefox 57 is happy because it sees schema 27 and everything is as it
     68 // expects.
     69 //
     70 // Firefox 56 non-DEBUG build is fine/happy, but DEBUG builds will not be.
     71 // - Our QuotaClient will invoke `NS_WARNING("Unknown Cache file found!");`
     72 //   at QuotaManager init time.  This is harmless but annoying and potentially
     73 //   misleading.
     74 // - The DEBUG-only Validate() call will error out whenever an attempt is made
     75 //   to open a DOM Cache database because it will notice the schema is broken
     76 //   and there is no attempt at recovery.
     77 //
     78 const int32_t kHackyDowngradeSchemaVersion = 25;
     79 const int32_t kHackyPaddingSizePresentVersion = 27;
     80 //
     81 // Update this whenever the DB schema is changed.
     82 const int32_t kLatestSchemaVersion = 29;
     83 // ---------
     84 // The following constants define the SQL schema.  These are defined in the
     85 // same order the SQL should be executed in CreateOrMigrateSchema().  They are
     86 // broken out as constants for convenient use in validation and migration.
     87 // ---------
     88 // The caches table is the single source of truth about what Cache
     89 // objects exist for the origin.  The contents of the Cache are stored
     90 // in the entries table that references back to caches.
     91 //
     92 // The caches table is also referenced from storage.  Rows in storage
     93 // represent named Cache objects.  There are cases, however, where
     94 // a Cache can still exist, but not be in a named Storage.  For example,
     95 // when content is still using the Cache after CacheStorage::Delete()
     96 // has been run.
     97 //
     98 // For now, the caches table mainly exists for data integrity with
     99 // foreign keys, but could be expanded to contain additional cache object
    100 // information.
    101 //
    102 // AUTOINCREMENT is necessary to prevent CacheId values from being reused.
    103 const char kTableCaches[] =
    104    "CREATE TABLE caches ("
    105    "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
    106    ")";
    107 
    108 // Security blobs are quite large and duplicated for every Response from
    109 // the same https origin.  This table is used to de-duplicate this data.
    110 const char kTableSecurityInfo[] =
    111    "CREATE TABLE security_info ("
    112    "id INTEGER NOT NULL PRIMARY KEY, "
    113    "hash BLOB NOT NULL, "  // first 8-bytes of the sha1 hash of data column
    114    "data BLOB NOT NULL, "  // full security info data, usually a few KB
    115    "refcount INTEGER NOT NULL"
    116    ")";
    117 
    118 // Index the smaller hash value instead of the large security data blob.
    119 const char kIndexSecurityInfoHash[] =
    120    "CREATE INDEX security_info_hash_index ON security_info (hash)";
    121 
    122 const char kTableEntries[] =
    123    "CREATE TABLE entries ("
    124    "id INTEGER NOT NULL PRIMARY KEY, "
    125    "request_method TEXT NOT NULL, "
    126    "request_url_no_query TEXT NOT NULL, "
    127    "request_url_no_query_hash BLOB NOT NULL, "  // first 8-bytes of sha1 hash
    128    "request_url_query TEXT NOT NULL, "
    129    "request_url_query_hash BLOB NOT NULL, "  // first 8-bytes of sha1 hash
    130    "request_referrer TEXT NOT NULL, "
    131    "request_headers_guard INTEGER NOT NULL, "
    132    "request_mode INTEGER NOT NULL, "
    133    "request_credentials INTEGER NOT NULL, "
    134    "request_contentpolicytype INTEGER NOT NULL, "
    135    "request_cache INTEGER NOT NULL, "
    136    "request_body_id TEXT NULL, "
    137    "response_type INTEGER NOT NULL, "
    138    "response_status INTEGER NOT NULL, "
    139    "response_status_text TEXT NOT NULL, "
    140    "response_headers_guard INTEGER NOT NULL, "
    141    "response_body_id TEXT NULL, "
    142    "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
    143    "response_principal_info TEXT NOT NULL, "
    144    "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
    145    "request_redirect INTEGER NOT NULL, "
    146    "request_referrer_policy INTEGER NOT NULL, "
    147    "request_integrity TEXT NOT NULL, "
    148    "request_url_fragment TEXT NOT NULL, "
    149    "response_padding_size INTEGER NULL, "
    150    "request_body_disk_size INTEGER NULL, "
    151    "response_body_disk_size INTEGER NULL "
    152    // New columns must be added at the end of table to migrate and
    153    // validate properly.
    154    ")";
    155 // Create an index to support the QueryCache() matching algorithm.  This
    156 // needs to quickly find entries in a given Cache that match the request
    157 // URL.  The url query is separated in order to support the ignoreSearch
    158 // option.  Finally, we index hashes of the URL values instead of the
    159 // actual strings to avoid excessive disk bloat.  The index will duplicate
    160 // the contents of the columsn in the index.  The hash index will prune
    161 // the vast majority of values from the query result so that normal
    162 // scanning only has to be done on a few values to find an exact URL match.
    163 const char kIndexEntriesRequest[] =
    164    "CREATE INDEX entries_request_match_index "
    165    "ON entries (cache_id, request_url_no_query_hash, "
    166    "request_url_query_hash)";
    167 
    168 const char kTableRequestHeaders[] =
    169    "CREATE TABLE request_headers ("
    170    "name TEXT NOT NULL, "
    171    "value TEXT NOT NULL, "
    172    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    173    ")";
    174 
    175 const char kTableResponseHeaders[] =
    176    "CREATE TABLE response_headers ("
    177    "name TEXT NOT NULL, "
    178    "value TEXT NOT NULL, "
    179    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    180    ")";
    181 
    182 // We need an index on response_headers, but not on request_headers,
    183 // because we quickly need to determine if a VARY header is present.
    184 const char kIndexResponseHeadersName[] =
    185    "CREATE INDEX response_headers_name_index "
    186    "ON response_headers (name)";
    187 
    188 const char kTableResponseUrlList[] =
    189    "CREATE TABLE response_url_list ("
    190    "url TEXT NOT NULL, "
    191    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    192    ")";
    193 
    194 // NOTE: key allows NULL below since that is how "" is represented
    195 //       in a BLOB column.  We use BLOB to avoid encoding issues
    196 //       with storing DOMStrings.
    197 const char kTableStorage[] =
    198    "CREATE TABLE storage ("
    199    "namespace INTEGER NOT NULL, "
    200    "key BLOB NULL, "
    201    "cache_id INTEGER NOT NULL REFERENCES caches(id), "
    202    "PRIMARY KEY(namespace, key) "
    203    ")";
    204 
    205 const char kTableUsageInfo[] =
    206    "CREATE TABLE usage_info ("
    207    "id INTEGER NOT NULL PRIMARY KEY, "
    208    "total_disk_usage INTEGER NOT NULL "
    209    ")";
    210 
    211 const char kTriggerEntriesInsert[] =
    212    "CREATE TRIGGER entries_insert_trigger "
    213    "AFTER INSERT ON entries "
    214    "FOR EACH ROW "
    215    "BEGIN "
    216    "UPDATE usage_info SET total_disk_usage = total_disk_usage + "
    217    "ifnull(NEW.request_body_disk_size, 0) + "
    218    "ifnull(NEW.response_body_disk_size, 0) "
    219    "WHERE usage_info.id = 1; "
    220    "END";
    221 
    222 const char kTriggerEntriesUpdate[] =
    223    "CREATE TRIGGER entries_update_trigger "
    224    "AFTER UPDATE ON entries "
    225    "FOR EACH ROW "
    226    "BEGIN "
    227    "UPDATE usage_info SET total_disk_usage = total_disk_usage - "
    228    "ifnull(OLD.request_body_disk_size, 0) + "
    229    "ifnull(NEW.request_body_disk_size, 0) - "
    230    "ifnull(OLD.response_body_disk_size, 0) + "
    231    "ifnull(NEW.response_body_disk_size, 0) "
    232    "WHERE usage_info.id = 1; "
    233    "END";
    234 
    235 const char kTriggerEntriesDelete[] =
    236    "CREATE TRIGGER entries_delete_trigger "
    237    "AFTER DELETE ON entries "
    238    "FOR EACH ROW "
    239    "BEGIN "
    240    "UPDATE usage_info SET total_disk_usage = total_disk_usage - "
    241    "ifnull(OLD.request_body_disk_size, 0) - "
    242    "ifnull(OLD.response_body_disk_size, 0) "
    243    "WHERE usage_info.id = 1; "
    244    "END";
    245 
    246 // ---------
    247 // End schema definition
    248 // ---------
    249 
    250 const uint32_t kMaxEntriesPerStatement = 255;
    251 
    252 const uint32_t kPageSize = 4 * 1024;
    253 
    254 // Grow the database in chunks to reduce fragmentation
    255 const uint32_t kGrowthSize = 32 * 1024;
    256 const uint32_t kGrowthPages = kGrowthSize / kPageSize;
    257 static_assert(kGrowthSize % kPageSize == 0,
    258              "Growth size must be multiple of page size");
    259 
    260 // Only release free pages when we have more than this limit
    261 const int32_t kMaxFreePages = kGrowthPages;
    262 
    263 // Limit WAL journal to a reasonable size
    264 const uint32_t kWalAutoCheckpointSize = 512 * 1024;
    265 const uint32_t kWalAutoCheckpointPages = kWalAutoCheckpointSize / kPageSize;
    266 static_assert(kWalAutoCheckpointSize % kPageSize == 0,
    267              "WAL checkpoint size must be multiple of page size");
    268 
    269 }  // namespace
    270 
    271 // If any of the static_asserts below fail, it means that you have changed
    272 // the corresponding WebIDL enum in a way that may be incompatible with the
    273 // existing data stored in the DOM Cache.  You would need to update the Cache
    274 // database schema accordingly and adjust the failing static_assert.
    275 static_assert(int(HeadersGuardEnum::None) == 0 &&
    276                  int(HeadersGuardEnum::Request) == 1 &&
    277                  int(HeadersGuardEnum::Request_no_cors) == 2 &&
    278                  int(HeadersGuardEnum::Response) == 3 &&
    279                  int(HeadersGuardEnum::Immutable) == 4 &&
    280                  ContiguousEnumSize<HeadersGuardEnum>::value == 5,
    281              "HeadersGuardEnum values are as expected");
    282 static_assert(int(ReferrerPolicy::_empty) == 0 &&
    283                  int(ReferrerPolicy::No_referrer) == 1 &&
    284                  int(ReferrerPolicy::No_referrer_when_downgrade) == 2 &&
    285                  int(ReferrerPolicy::Origin) == 3 &&
    286                  int(ReferrerPolicy::Origin_when_cross_origin) == 4 &&
    287                  int(ReferrerPolicy::Unsafe_url) == 5 &&
    288                  int(ReferrerPolicy::Same_origin) == 6 &&
    289                  int(ReferrerPolicy::Strict_origin) == 7 &&
    290                  int(ReferrerPolicy::Strict_origin_when_cross_origin) == 8 &&
    291                  ContiguousEnumSize<ReferrerPolicy>::value == 9,
    292              "ReferrerPolicy values are as expected");
    293 static_assert(int(RequestMode::Same_origin) == 0 &&
    294                  int(RequestMode::No_cors) == 1 &&
    295                  int(RequestMode::Cors) == 2 &&
    296                  int(RequestMode::Navigate) == 3 &&
    297                  ContiguousEnumSize<RequestMode>::value == 4,
    298              "RequestMode values are as expected");
    299 static_assert(int(RequestCredentials::Omit) == 0 &&
    300                  int(RequestCredentials::Same_origin) == 1 &&
    301                  int(RequestCredentials::Include) == 2 &&
    302                  ContiguousEnumSize<RequestCredentials>::value == 3,
    303              "RequestCredentials values are as expected");
    304 static_assert(int(RequestCache::Default) == 0 &&
    305                  int(RequestCache::No_store) == 1 &&
    306                  int(RequestCache::Reload) == 2 &&
    307                  int(RequestCache::No_cache) == 3 &&
    308                  int(RequestCache::Force_cache) == 4 &&
    309                  int(RequestCache::Only_if_cached) == 5 &&
    310                  ContiguousEnumSize<RequestCache>::value == 6,
    311              "RequestCache values are as expected");
    312 static_assert(int(RequestRedirect::Follow) == 0 &&
    313                  int(RequestRedirect::Error) == 1 &&
    314                  int(RequestRedirect::Manual) == 2 &&
    315                  ContiguousEnumSize<RequestRedirect>::value == 3,
    316              "RequestRedirect values are as expected");
    317 static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 &&
    318                  int(ResponseType::Default) == 2 &&
    319                  int(ResponseType::Error) == 3 &&
    320                  int(ResponseType::Opaque) == 4 &&
    321                  int(ResponseType::Opaqueredirect) == 5 &&
    322                  ContiguousEnumSize<ResponseType>::value == 6,
    323              "ResponseType values are as expected");
    324 
    325 // If the static_asserts below fails, it means that you have changed the
    326 // Namespace enum in a way that may be incompatible with the existing data
    327 // stored in the DOM Cache.  You would need to update the Cache database schema
    328 // accordingly and adjust the failing static_assert.
    329 static_assert(DEFAULT_NAMESPACE == 0 && CHROME_ONLY_NAMESPACE == 1 &&
    330                  NUMBER_OF_NAMESPACES == 2,
    331              "Namespace values are as expected");
    332 
    333 // If the static_asserts below fails, it means that you have changed the
    334 // nsContentPolicy enum in a way that may be incompatible with the existing data
    335 // stored in the DOM Cache.  You would need to update the Cache database schema
    336 // accordingly and adjust the failing static_assert.
    337 static_assert(
    338    nsIContentPolicy::TYPE_INVALID == 0 && nsIContentPolicy::TYPE_OTHER == 1 &&
    339        nsIContentPolicy::TYPE_SCRIPT == 2 &&
    340        nsIContentPolicy::TYPE_IMAGE == 3 &&
    341        nsIContentPolicy::TYPE_STYLESHEET == 4 &&
    342        nsIContentPolicy::TYPE_OBJECT == 5 &&
    343        nsIContentPolicy::TYPE_DOCUMENT == 6 &&
    344        nsIContentPolicy::TYPE_SUBDOCUMENT == 7 &&
    345        nsIContentPolicy::TYPE_PING == 10 &&
    346        nsIContentPolicy::TYPE_XMLHTTPREQUEST == 11 &&
    347        nsIContentPolicy::TYPE_DTD == 13 && nsIContentPolicy::TYPE_FONT == 14 &&
    348        nsIContentPolicy::TYPE_MEDIA == 15 &&
    349        nsIContentPolicy::TYPE_WEBSOCKET == 16 &&
    350        nsIContentPolicy::TYPE_CSP_REPORT == 17 &&
    351        nsIContentPolicy::TYPE_XSLT == 18 &&
    352        nsIContentPolicy::TYPE_BEACON == 19 &&
    353        nsIContentPolicy::TYPE_FETCH == 20 &&
    354        nsIContentPolicy::TYPE_IMAGESET == 21 &&
    355        nsIContentPolicy::TYPE_WEB_MANIFEST == 22 &&
    356        nsIContentPolicy::TYPE_INTERNAL_SCRIPT == 23 &&
    357        nsIContentPolicy::TYPE_INTERNAL_WORKER == 24 &&
    358        nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER == 25 &&
    359        nsIContentPolicy::TYPE_INTERNAL_EMBED == 26 &&
    360        nsIContentPolicy::TYPE_INTERNAL_OBJECT == 27 &&
    361        nsIContentPolicy::TYPE_INTERNAL_FRAME == 28 &&
    362        nsIContentPolicy::TYPE_INTERNAL_IFRAME == 29 &&
    363        nsIContentPolicy::TYPE_INTERNAL_AUDIO == 30 &&
    364        nsIContentPolicy::TYPE_INTERNAL_VIDEO == 31 &&
    365        nsIContentPolicy::TYPE_INTERNAL_TRACK == 32 &&
    366        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC == 33 &&
    367        nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34 &&
    368        nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER == 35 &&
    369        nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD == 36 &&
    370        nsIContentPolicy::TYPE_INTERNAL_IMAGE == 37 &&
    371        nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD == 38 &&
    372        nsIContentPolicy::TYPE_INTERNAL_STYLESHEET == 39 &&
    373        nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD == 40 &&
    374        nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON == 41 &&
    375        nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS == 42 &&
    376        nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD == 43 &&
    377        nsIContentPolicy::TYPE_SPECULATIVE == 44 &&
    378        nsIContentPolicy::TYPE_INTERNAL_MODULE == 45 &&
    379        nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD == 46 &&
    380        nsIContentPolicy::TYPE_INTERNAL_DTD == 47 &&
    381        nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD == 48 &&
    382        nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET == 49 &&
    383        nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET == 50 &&
    384        nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD == 51 &&
    385        nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT == 52 &&
    386        nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT == 53 &&
    387        nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD == 54 &&
    388        nsIContentPolicy::TYPE_UA_FONT == 55 &&
    389        nsIContentPolicy::TYPE_WEB_IDENTITY == 57 &&
    390        nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE == 58 &&
    391        nsIContentPolicy::TYPE_WEB_TRANSPORT == 59 &&
    392        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC == 60 &&
    393        nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE == 61 &&
    394        nsIContentPolicy::TYPE_JSON == 62 &&
    395        nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD == 63 &&
    396        nsIContentPolicy::TYPE_END == 64,
    397    "nsContentPolicyType values are as expected");
    398 
    399 namespace {
    400 
    401 using EntryId = int32_t;
    402 
    403 struct IdCount {
    404  explicit IdCount(int32_t aId) : mId(aId), mCount(1) {}
    405  int32_t mId;
    406  int32_t mCount;
    407 };
    408 
    409 using EntryIds = AutoTArray<EntryId, 256>;
    410 
    411 static Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn,
    412                                           CacheId aCacheId);
    413 static Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn,
    414                                             CacheId aCacheId,
    415                                             const CacheRequest& aRequest,
    416                                             const CacheQueryParams& aParams,
    417                                             uint32_t aMaxResults = UINT32_MAX);
    418 static Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn,
    419                                                const CacheRequest& aRequest,
    420                                                EntryId entryId);
    421 // Returns a success tuple containing the deleted body ids, deleted security ids
    422 // and deleted padding size.
    423 static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>,
    424              nsresult>
    425 DeleteEntries(mozIStorageConnection& aConn,
    426              const nsTArray<EntryId>& aEntryIdList);
    427 
    428 static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>,
    429              nsresult>
    430 DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId);
    431 
    432 static Result<int32_t, nsresult> InsertSecurityInfo(
    433    mozIStorageConnection& aConn, nsICryptoHash& aCrypto,
    434    nsITransportSecurityInfo* aSecurityInfo);
    435 static nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId,
    436                                   int32_t aCount);
    437 static nsresult DeleteSecurityInfoList(
    438    mozIStorageConnection& aConn,
    439    const nsTArray<IdCount>& aDeletedStorageIdList);
    440 static nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId,
    441                            const CacheRequest& aRequest,
    442                            const nsID* aRequestBodyId,
    443                            const CacheResponse& aResponse,
    444                            const nsID* aResponseBodyId);
    445 static Result<SavedResponse, nsresult> ReadResponse(
    446    mozIStorageConnection& aConn, EntryId aEntryId);
    447 static Result<SavedRequest, nsresult> ReadRequest(mozIStorageConnection& aConn,
    448                                                  EntryId aEntryId);
    449 
    450 static void AppendListParamsToQuery(nsACString& aQuery, size_t aLen);
    451 static nsresult BindListParamsToQuery(mozIStorageStatement& aState,
    452                                      const Span<const EntryId>& aEntryIdList);
    453 static nsresult BindId(mozIStorageStatement& aState, const nsACString& aName,
    454                       const nsID* aId);
    455 static Result<nsID, nsresult> ExtractId(mozIStorageStatement& aState,
    456                                        uint32_t aPos);
    457 static Result<NotNull<nsCOMPtr<mozIStorageStatement>>, nsresult>
    458 CreateAndBindKeyStatement(mozIStorageConnection& aConn,
    459                          const char* aQueryFormat, const nsAString& aKey);
    460 static Result<nsAutoCString, nsresult> HashCString(nsICryptoHash& aCrypto,
    461                                                   const nsACString& aIn);
    462 Result<int32_t, nsresult> GetEffectiveSchemaVersion(
    463    mozIStorageConnection& aConn);
    464 nsresult Validate(mozIStorageConnection& aConn);
    465 nsresult Migrate(nsIFile& aDBDir, mozIStorageConnection& aConn);
    466 }  // namespace
    467 
    468 class MOZ_RAII AutoDisableForeignKeyChecking {
    469 public:
    470  explicit AutoDisableForeignKeyChecking(mozIStorageConnection* aConn)
    471      : mConn(aConn), mForeignKeyCheckingDisabled(false) {
    472    QM_TRY_INSPECT(const auto& state,
    473                   quota::CreateAndExecuteSingleStepStatement(
    474                       *mConn, "PRAGMA foreign_keys;"_ns),
    475                   QM_VOID);
    476 
    477    QM_TRY_INSPECT(const int32_t& mode,
    478                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0), QM_VOID);
    479 
    480    if (mode) {
    481      QM_WARNONLY_TRY(MOZ_TO_RESULT(mConn->ExecuteSimpleSQL(
    482                                        "PRAGMA foreign_keys = OFF;"_ns))
    483                          .andThen([this](const auto) -> Result<Ok, nsresult> {
    484                            mForeignKeyCheckingDisabled = true;
    485                            return Ok{};
    486                          }));
    487    }
    488  }
    489 
    490  ~AutoDisableForeignKeyChecking() {
    491    if (mForeignKeyCheckingDisabled) {
    492      QM_WARNONLY_TRY(QM_TO_RESULT(
    493          mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
    494    }
    495  }
    496 
    497 private:
    498  nsCOMPtr<mozIStorageConnection> mConn;
    499  bool mForeignKeyCheckingDisabled;
    500 };
    501 
    502 nsresult CreateOrMigrateSchema(nsIFile& aDBDir, mozIStorageConnection& aConn) {
    503  MOZ_ASSERT(!NS_IsMainThread());
    504 
    505  QM_TRY_UNWRAP(int32_t schemaVersion, GetEffectiveSchemaVersion(aConn));
    506 
    507  if (schemaVersion == kLatestSchemaVersion) {
    508    // We already have the correct schema version.  Validate it matches
    509    // our expected schema and then proceed.
    510    QM_TRY(MOZ_TO_RESULT(Validate(aConn)));
    511 
    512    return NS_OK;
    513  }
    514 
    515  // Turn off checking foreign keys before starting a transaction, and restore
    516  // it once we're done.
    517  AutoDisableForeignKeyChecking restoreForeignKeyChecking(&aConn);
    518  mozStorageTransaction trans(&aConn, false,
    519                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
    520 
    521  QM_TRY(MOZ_TO_RESULT(trans.Start()));
    522 
    523  const bool migrating = schemaVersion != 0;
    524 
    525  if (migrating) {
    526    // A schema exists, but its not the current version.  Attempt to
    527    // migrate it to our new schema.
    528    QM_TRY(MOZ_TO_RESULT(Migrate(aDBDir, aConn)));
    529  } else {
    530    // There is no schema installed.  Create the database from scratch.
    531    QM_TRY(
    532        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableCaches))));
    533    QM_TRY(MOZ_TO_RESULT(
    534        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableSecurityInfo))));
    535    QM_TRY(MOZ_TO_RESULT(
    536        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexSecurityInfoHash))));
    537    QM_TRY(
    538        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableEntries))));
    539    QM_TRY(MOZ_TO_RESULT(
    540        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexEntriesRequest))));
    541    QM_TRY(MOZ_TO_RESULT(
    542        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableRequestHeaders))));
    543    QM_TRY(MOZ_TO_RESULT(
    544        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseHeaders))));
    545    QM_TRY(MOZ_TO_RESULT(
    546        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexResponseHeadersName))));
    547    QM_TRY(MOZ_TO_RESULT(
    548        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseUrlList))));
    549    QM_TRY(
    550        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableStorage))));
    551    QM_TRY(MOZ_TO_RESULT(
    552        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableUsageInfo))));
    553    QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
    554        nsLiteralCString("INSERT INTO usage_info VALUES(1, 0);"))));
    555    QM_TRY(MOZ_TO_RESULT(
    556        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesInsert))));
    557    QM_TRY(MOZ_TO_RESULT(
    558        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesUpdate))));
    559    QM_TRY(MOZ_TO_RESULT(
    560        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesDelete))));
    561    QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(kLatestSchemaVersion)));
    562    QM_TRY_UNWRAP(schemaVersion, GetEffectiveSchemaVersion(aConn));
    563  }
    564 
    565  QM_TRY(MOZ_TO_RESULT(Validate(aConn)));
    566  QM_TRY(MOZ_TO_RESULT(trans.Commit()));
    567 
    568  if (migrating) {
    569    // Migrations happen infrequently and reflect a chance in DB structure.
    570    // This is a good time to rebuild the database.  It also helps catch
    571    // if a new migration is incorrect by fast failing on the corruption.
    572    // Unfortunately, this must be performed outside of the transaction.
    573 
    574    QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("VACUUM"_ns)));
    575  }
    576 
    577  return NS_OK;
    578 }
    579 
    580 nsresult InitializeConnection(mozIStorageConnection& aConn) {
    581  MOZ_ASSERT(!NS_IsMainThread());
    582 
    583  // This function needs to perform per-connection initialization tasks that
    584  // need to happen regardless of the schema.
    585 
    586  // Note, the default encoding of UTF-8 is preferred.  mozStorage does all
    587  // the work necessary to convert UTF-16 nsString values for us.  We don't
    588  // need ordering and the binary equality operations are correct.  So, do
    589  // NOT set PRAGMA encoding to UTF-16.
    590 
    591  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString(
    592      // Use a smaller page size to improve perf/footprint; default is too large
    593      "PRAGMA page_size = %u; "
    594      // Enable auto_vacuum; this must happen after page_size and before WAL
    595      "PRAGMA auto_vacuum = INCREMENTAL; "
    596      "PRAGMA foreign_keys = ON; ",
    597      kPageSize))));
    598 
    599  // Limit fragmentation by growing the database by many pages at once.
    600  QM_TRY(QM_OR_ELSE_WARN_IF(
    601      // Expression.
    602      MOZ_TO_RESULT(aConn.SetGrowthIncrement(kGrowthSize, ""_ns)),
    603      // Predicate.
    604      IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
    605      // Fallback.
    606      ErrToDefaultOk<>));
    607 
    608  // Enable WAL journaling.  This must be performed in a separate transaction
    609  // after changing the page_size and enabling auto_vacuum.
    610  // Note there is a default journal_size_limit set by mozStorage.
    611  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString(
    612      // WAL journal can grow to given number of *pages*
    613      "PRAGMA wal_autocheckpoint = %u; "
    614      // WAL must be enabled at the end to allow page size to be changed, etc.
    615      "PRAGMA journal_mode = WAL; ",
    616      kWalAutoCheckpointPages))));
    617 
    618  // Verify that we successfully set the vacuum mode to incremental.  It
    619  // is very easy to put the database in a state where the auto_vacuum
    620  // pragma above fails silently.
    621 #ifdef DEBUG
    622  {
    623    QM_TRY_INSPECT(const auto& state,
    624                   quota::CreateAndExecuteSingleStepStatement(
    625                       aConn, "PRAGMA auto_vacuum;"_ns));
    626 
    627    QM_TRY_INSPECT(const int32_t& mode,
    628                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
    629 
    630    // integer value 2 is incremental mode
    631    QM_TRY(OkIf(mode == 2), NS_ERROR_UNEXPECTED);
    632  }
    633 #endif
    634 
    635  return NS_OK;
    636 }
    637 
    638 Result<CacheId, nsresult> CreateCacheId(mozIStorageConnection& aConn) {
    639  MOZ_ASSERT(!NS_IsMainThread());
    640 
    641  QM_TRY(MOZ_TO_RESULT(
    642      aConn.ExecuteSimpleSQL("INSERT INTO caches DEFAULT VALUES;"_ns)));
    643 
    644  QM_TRY_INSPECT(const auto& state,
    645                 quota::CreateAndExecuteSingleStepStatement<
    646                     quota::SingleStepResult::ReturnNullIfNoResult>(
    647                     aConn, "SELECT last_insert_rowid()"_ns));
    648 
    649  QM_TRY(OkIf(state), Err(NS_ERROR_UNEXPECTED));
    650 
    651  QM_TRY_INSPECT(const CacheId& id,
    652                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt64, 0));
    653 
    654  return id;
    655 }
    656 
    657 Result<DeletionInfo, nsresult> DeleteCacheId(mozIStorageConnection& aConn,
    658                                             CacheId aCacheId) {
    659  MOZ_ASSERT(!NS_IsMainThread());
    660 
    661  // XXX only deletedBodyIdList needs to be non-const
    662  QM_TRY_UNWRAP(
    663      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
    664      DeleteAllCacheEntries(aConn, aCacheId));
    665 
    666  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));
    667 
    668  // Delete the remainder of the cache using cascade semantics.
    669  QM_TRY_INSPECT(const auto& state,
    670                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    671                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    672                     "DELETE FROM caches WHERE id=:id;"_ns));
    673 
    674  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("id"_ns, aCacheId)));
    675 
    676  QM_TRY(MOZ_TO_RESULT(state->Execute()));
    677 
    678  return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize};
    679 }
    680 
    681 Result<AutoTArray<CacheId, 8>, nsresult> FindOrphanedCacheIds(
    682    mozIStorageConnection& aConn) {
    683  QM_TRY_INSPECT(const auto& state,
    684                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    685                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    686                     "SELECT id FROM caches "
    687                     "WHERE id NOT IN (SELECT cache_id from storage);"_ns));
    688 
    689  QM_TRY_RETURN(
    690      (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 8>>(
    691          *state, [](auto& stmt) {
    692            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
    693          })));
    694 }
    695 
    696 Result<int64_t, nsresult> FindOverallPaddingSize(mozIStorageConnection& aConn) {
    697  QM_TRY_INSPECT(const auto& state,
    698                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    699                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    700                     "SELECT response_padding_size FROM entries "
    701                     "WHERE response_padding_size IS NOT NULL;"_ns));
    702 
    703  int64_t overallPaddingSize = 0;
    704 
    705  QM_TRY(quota::CollectWhileHasResult(
    706      *state, [&overallPaddingSize](auto& stmt) -> Result<Ok, nsresult> {
    707        QM_TRY_INSPECT(const int64_t& padding_size,
    708                       MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
    709 
    710        MOZ_DIAGNOSTIC_ASSERT(padding_size >= 0);
    711        MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - padding_size >= overallPaddingSize);
    712        overallPaddingSize += padding_size;
    713 
    714        return Ok{};
    715      }));
    716 
    717  return overallPaddingSize;
    718 }
    719 
    720 Result<int64_t, nsresult> GetTotalDiskUsage(mozIStorageConnection& aConn) {
    721  QM_TRY_INSPECT(
    722      const auto& state,
    723      quota::CreateAndExecuteSingleStepStatement(
    724          aConn, "SELECT total_disk_usage FROM usage_info WHERE id = 1;"_ns));
    725 
    726  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0));
    727 }
    728 
    729 Result<nsTHashSet<nsID>, nsresult> GetKnownBodyIds(
    730    mozIStorageConnection& aConn) {
    731  MOZ_ASSERT(!NS_IsMainThread());
    732 
    733  int32_t numEntries = 0;
    734  {
    735    QM_TRY_INSPECT(const auto& cnt,
    736                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    737                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    738                       "SELECT COUNT(*) FROM entries;"_ns));
    739 
    740    QM_TRY(quota::CollectWhileHasResult(
    741        *cnt, [&numEntries](auto& stmt) -> Result<Ok, nsresult> {
    742          QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(0, &numEntries)));
    743 
    744          return Ok{};
    745        }));
    746  }
    747 
    748  // Each row can have 0 to 2 nsID values, prepare for the maximum.
    749  nsTHashSet<nsID> idSet(numEntries * 2);
    750 
    751  QM_TRY_INSPECT(
    752      const auto& state,
    753      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    754          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    755          "SELECT request_body_id, response_body_id FROM entries;"_ns));
    756 
    757  QM_TRY(quota::CollectWhileHasResult(
    758      *state, [&idSet](auto& stmt) -> Result<Ok, nsresult> {
    759        // extract 0 to 2 nsID structs per row
    760        for (uint32_t i = 0; i < 2; ++i) {
    761          QM_TRY_INSPECT(const bool& isNull,
    762                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));
    763 
    764          if (!isNull) {
    765            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));
    766 
    767            idSet.Insert(id);
    768          }
    769        }
    770 
    771        return Ok{};
    772      }));
    773 
    774  return std::move(idSet);
    775 }
    776 
    777 Result<Maybe<SavedResponse>, nsresult> CacheMatch(
    778    mozIStorageConnection& aConn, CacheId aCacheId,
    779    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
    780  MOZ_ASSERT(!NS_IsMainThread());
    781 
    782  QM_TRY_INSPECT(const auto& matches,
    783                 QueryCache(aConn, aCacheId, aRequest, aParams, 1));
    784 
    785  if (matches.IsEmpty()) {
    786    return Maybe<SavedResponse>();
    787  }
    788 
    789  QM_TRY_UNWRAP(auto response, ReadResponse(aConn, matches[0]));
    790 
    791  response.mCacheId = aCacheId;
    792 
    793  return Some(std::move(response));
    794 }
    795 
    796 Result<nsTArray<SavedResponse>, nsresult> CacheMatchAll(
    797    mozIStorageConnection& aConn, CacheId aCacheId,
    798    const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) {
    799  MOZ_ASSERT(!NS_IsMainThread());
    800 
    801  QM_TRY_INSPECT(
    802      const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] {
    803        if (aMaybeRequest.isNothing()) {
    804          QM_TRY_RETURN(QueryAll(aConn, aCacheId));
    805        }
    806 
    807        QM_TRY_RETURN(
    808            QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams));
    809      }()));
    810 
    811  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
    812  QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr(
    813      matches,
    814      [&aConn, aCacheId](const auto match) -> Result<SavedResponse, nsresult> {
    815        QM_TRY_UNWRAP(auto savedResponse, ReadResponse(aConn, match));
    816 
    817        savedResponse.mCacheId = aCacheId;
    818        return savedResponse;
    819      },
    820      fallible));
    821 }
    822 
    823 Result<DeletionInfo, nsresult> CachePut(mozIStorageConnection& aConn,
    824                                        CacheId aCacheId,
    825                                        const CacheRequest& aRequest,
    826                                        const nsID* aRequestBodyId,
    827                                        const CacheResponse& aResponse,
    828                                        const nsID* aResponseBodyId) {
    829  MOZ_ASSERT(!NS_IsMainThread());
    830 
    831  QM_TRY_INSPECT(
    832      const auto& matches,
    833      QueryCache(aConn, aCacheId, aRequest,
    834                 CacheQueryParams(false, false, false, false, u""_ns)));
    835 
    836  // XXX only deletedBodyIdList needs to be non-const
    837  QM_TRY_UNWRAP(
    838      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
    839      DeleteEntries(aConn, matches));
    840 
    841  QM_TRY(MOZ_TO_RESULT(InsertEntry(aConn, aCacheId, aRequest, aRequestBodyId,
    842                                   aResponse, aResponseBodyId)));
    843 
    844  // Delete the security values after doing the insert to avoid churning
    845  // the security table when its not necessary.
    846  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));
    847 
    848  return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize};
    849 }
    850 
    851 Result<Maybe<DeletionInfo>, nsresult> CacheDelete(
    852    mozIStorageConnection& aConn, CacheId aCacheId,
    853    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
    854  MOZ_ASSERT(!NS_IsMainThread());
    855 
    856  QM_TRY_INSPECT(const auto& matches,
    857                 QueryCache(aConn, aCacheId, aRequest, aParams));
    858 
    859  if (matches.IsEmpty()) {
    860    return Maybe<DeletionInfo>();
    861  }
    862 
    863  // XXX only deletedBodyIdList needs to be non-const
    864  QM_TRY_UNWRAP(
    865      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
    866      DeleteEntries(aConn, matches));
    867 
    868  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));
    869 
    870  return Some(DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize});
    871 }
    872 
    873 Result<nsTArray<SavedRequest>, nsresult> CacheKeys(
    874    mozIStorageConnection& aConn, CacheId aCacheId,
    875    const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) {
    876  MOZ_ASSERT(!NS_IsMainThread());
    877 
    878  QM_TRY_INSPECT(
    879      const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] {
    880        if (aMaybeRequest.isNothing()) {
    881          QM_TRY_RETURN(QueryAll(aConn, aCacheId));
    882        }
    883 
    884        QM_TRY_RETURN(
    885            QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams));
    886      }()));
    887 
    888  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
    889  QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr(
    890      matches,
    891      [&aConn, aCacheId](const auto match) -> Result<SavedRequest, nsresult> {
    892        QM_TRY_UNWRAP(auto savedRequest, ReadRequest(aConn, match));
    893 
    894        savedRequest.mCacheId = aCacheId;
    895        return savedRequest;
    896      },
    897      fallible));
    898 }
    899 
    900 Result<Maybe<SavedResponse>, nsresult> StorageMatch(
    901    mozIStorageConnection& aConn, Namespace aNamespace,
    902    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
    903  MOZ_ASSERT(!NS_IsMainThread());
    904 
    905  // If we are given a cache to check, then simply find its cache ID
    906  // and perform the match.
    907  if (aParams.cacheNameSet()) {
    908    QM_TRY_INSPECT(const auto& maybeCacheId,
    909                   StorageGetCacheId(aConn, aNamespace, aParams.cacheName()));
    910    if (maybeCacheId.isNothing()) {
    911      return Maybe<SavedResponse>();
    912    }
    913 
    914    return CacheMatch(aConn, maybeCacheId.ref(), aRequest, aParams);
    915  }
    916 
    917  // Otherwise we need to get a list of all the cache IDs in this namespace.
    918 
    919  QM_TRY_INSPECT(const auto& state,
    920                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    921                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    922                     "SELECT cache_id FROM storage WHERE "
    923                     "namespace=:namespace ORDER BY rowid;"_ns));
    924 
    925  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
    926 
    927  QM_TRY_INSPECT(
    928      const auto& cacheIdList,
    929      (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 32>>(
    930          *state, [](auto& stmt) {
    931            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
    932          })));
    933 
    934  // Now try to find a match in each cache in order
    935  for (const auto cacheId : cacheIdList) {
    936    QM_TRY_UNWRAP(auto matchedResponse,
    937                  CacheMatch(aConn, cacheId, aRequest, aParams));
    938 
    939    if (matchedResponse.isSome()) {
    940      return matchedResponse;
    941    }
    942  }
    943 
    944  return Maybe<SavedResponse>();
    945 }
    946 
    947 Result<Maybe<CacheId>, nsresult> StorageGetCacheId(mozIStorageConnection& aConn,
    948                                                   Namespace aNamespace,
    949                                                   const nsAString& aKey) {
    950  MOZ_ASSERT(!NS_IsMainThread());
    951 
    952  // How we constrain the key column depends on the value of our key.  Use
    953  // a format string for the query and let CreateAndBindKeyStatement() fill
    954  // it in for us.
    955  const char* const query =
    956      "SELECT cache_id FROM storage "
    957      "WHERE namespace=:namespace AND %s "
    958      "ORDER BY rowid;";
    959 
    960  QM_TRY_INSPECT(const auto& state,
    961                 CreateAndBindKeyStatement(aConn, query, aKey));
    962 
    963  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
    964 
    965  QM_TRY_INSPECT(const bool& hasMoreData,
    966                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, ExecuteStep));
    967 
    968  if (!hasMoreData) {
    969    return Maybe<CacheId>();
    970  }
    971 
    972  QM_TRY_RETURN(
    973      MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0).map(Some<CacheId>));
    974 }
    975 
    976 nsresult StoragePutCache(mozIStorageConnection& aConn, Namespace aNamespace,
    977                         const nsAString& aKey, CacheId aCacheId) {
    978  MOZ_ASSERT(!NS_IsMainThread());
    979 
    980  QM_TRY_INSPECT(const auto& state,
    981                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    982                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
    983                     "INSERT INTO storage (namespace, key, cache_id) "
    984                     "VALUES (:namespace, :key, :cache_id);"_ns));
    985 
    986  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
    987  QM_TRY(MOZ_TO_RESULT(state->BindStringAsBlobByName("key"_ns, aKey)));
    988  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
    989  QM_TRY(MOZ_TO_RESULT(state->Execute()));
    990 
    991  return NS_OK;
    992 }
    993 
    994 nsresult StorageForgetCache(mozIStorageConnection& aConn, Namespace aNamespace,
    995                            const nsAString& aKey) {
    996  MOZ_ASSERT(!NS_IsMainThread());
    997 
    998  // How we constrain the key column depends on the value of our key.  Use
    999  // a format string for the query and let CreateAndBindKeyStatement() fill
   1000  // it in for us.
   1001  const char* const query =
   1002      "DELETE FROM storage WHERE namespace=:namespace AND %s;";
   1003 
   1004  QM_TRY_INSPECT(const auto& state,
   1005                 CreateAndBindKeyStatement(aConn, query, aKey));
   1006 
   1007  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
   1008 
   1009  QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1010 
   1011  return NS_OK;
   1012 }
   1013 
   1014 Result<nsTArray<nsString>, nsresult> StorageGetKeys(
   1015    mozIStorageConnection& aConn, Namespace aNamespace) {
   1016  MOZ_ASSERT(!NS_IsMainThread());
   1017 
   1018  QM_TRY_INSPECT(
   1019      const auto& state,
   1020      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1021          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1022          "SELECT key FROM storage WHERE namespace=:namespace ORDER BY rowid;"_ns));
   1023 
   1024  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
   1025 
   1026  QM_TRY_RETURN(quota::CollectElementsWhileHasResult(*state, [](auto& stmt) {
   1027    QM_TRY_RETURN(
   1028        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetBlobAsString, 0));
   1029  }));
   1030 }
   1031 
   1032 namespace {
   1033 
   1034 Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn,
   1035                                    CacheId aCacheId) {
   1036  MOZ_ASSERT(!NS_IsMainThread());
   1037 
   1038  QM_TRY_INSPECT(
   1039      const auto& state,
   1040      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1041          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1042          "SELECT id FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns));
   1043 
   1044  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
   1045 
   1046  QM_TRY_RETURN((quota::CollectElementsWhileHasResultTyped<EntryIds>(
   1047      *state, [](auto& stmt) {
   1048        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
   1049      })));
   1050 }
   1051 
   1052 Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn,
   1053                                      CacheId aCacheId,
   1054                                      const CacheRequest& aRequest,
   1055                                      const CacheQueryParams& aParams,
   1056                                      uint32_t aMaxResults) {
   1057  MOZ_ASSERT(!NS_IsMainThread());
   1058  MOZ_DIAGNOSTIC_ASSERT(aMaxResults > 0);
   1059 
   1060  if (!aParams.ignoreMethod() &&
   1061      !aRequest.method().LowerCaseEqualsLiteral("get")) {
   1062    return Result<EntryIds, nsresult>{std::in_place};
   1063  }
   1064 
   1065  nsAutoCString query(
   1066      "SELECT id, COUNT(response_headers.name) AS vary_count, response_type "
   1067      "FROM entries "
   1068      "LEFT OUTER JOIN response_headers ON "
   1069      "entries.id=response_headers.entry_id "
   1070      "AND response_headers.name='vary' COLLATE NOCASE "
   1071      "WHERE entries.cache_id=:cache_id "
   1072      "AND entries.request_url_no_query_hash=:url_no_query_hash ");
   1073 
   1074  if (!aParams.ignoreSearch()) {
   1075    query.AppendLiteral("AND entries.request_url_query_hash=:url_query_hash ");
   1076  }
   1077 
   1078  query.AppendLiteral("AND entries.request_url_no_query=:url_no_query ");
   1079 
   1080  if (!aParams.ignoreSearch()) {
   1081    query.AppendLiteral("AND entries.request_url_query=:url_query ");
   1082  }
   1083 
   1084  query.AppendLiteral("GROUP BY entries.id ORDER BY entries.id;");
   1085 
   1086  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1087                                        nsCOMPtr<mozIStorageStatement>, aConn,
   1088                                        CreateStatement, query));
   1089 
   1090  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
   1091 
   1092  QM_TRY_INSPECT(const auto& crypto,
   1093                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>,
   1094                                         MOZ_SELECT_OVERLOAD(do_CreateInstance),
   1095                                         NS_CRYPTO_HASH_CONTRACTID));
   1096 
   1097  QM_TRY_INSPECT(const auto& urlWithoutQueryHash,
   1098                 HashCString(*crypto, aRequest.urlWithoutQuery()));
   1099 
   1100  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("url_no_query_hash"_ns,
   1101                                                         urlWithoutQueryHash)));
   1102 
   1103  if (!aParams.ignoreSearch()) {
   1104    QM_TRY_INSPECT(const auto& urlQueryHash,
   1105                   HashCString(*crypto, aRequest.urlQuery()));
   1106 
   1107    QM_TRY(MOZ_TO_RESULT(
   1108        state->BindUTF8StringAsBlobByName("url_query_hash"_ns, urlQueryHash)));
   1109  }
   1110 
   1111  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName(
   1112      "url_no_query"_ns, aRequest.urlWithoutQuery())));
   1113 
   1114  if (!aParams.ignoreSearch()) {
   1115    QM_TRY(MOZ_TO_RESULT(
   1116        state->BindUTF8StringByName("url_query"_ns, aRequest.urlQuery())));
   1117  }
   1118 
   1119  EntryIds entryIdList;
   1120 
   1121  QM_TRY(CollectWhile(
   1122      [&state, &entryIdList, aMaxResults]() -> Result<bool, nsresult> {
   1123        if (entryIdList.Length() == aMaxResults) {
   1124          return false;
   1125        }
   1126        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(state, ExecuteStep));
   1127      },
   1128      [&state, &entryIdList, &aParams, &aConn,
   1129       &aRequest]() -> Result<Ok, nsresult> {
   1130        QM_TRY_INSPECT(const EntryId& entryId,
   1131                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 0));
   1132 
   1133        QM_TRY_INSPECT(const int32_t& varyCount,
   1134                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 1));
   1135 
   1136        QM_TRY_INSPECT(const int32_t& responseType,
   1137                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 2));
   1138 
   1139        auto ignoreVary =
   1140            aParams.ignoreVary() ||
   1141            responseType == static_cast<int>(ResponseType::Opaque);
   1142 
   1143        if (!ignoreVary && varyCount > 0) {
   1144          QM_TRY_INSPECT(const bool& matchedByVary,
   1145                         MatchByVaryHeader(aConn, aRequest, entryId));
   1146          if (!matchedByVary) {
   1147            return Ok{};
   1148          }
   1149        }
   1150 
   1151        entryIdList.AppendElement(entryId);
   1152 
   1153        return Ok{};
   1154      }));
   1155 
   1156  return entryIdList;
   1157 }
   1158 
   1159 Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn,
   1160                                         const CacheRequest& aRequest,
   1161                                         EntryId entryId) {
   1162  MOZ_ASSERT(!NS_IsMainThread());
   1163 
   1164  QM_TRY_INSPECT(
   1165      const auto& varyValues,
   1166      ([&aConn, entryId]() -> Result<AutoTArray<nsCString, 8>, nsresult> {
   1167        QM_TRY_INSPECT(
   1168            const auto& state,
   1169            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1170                nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1171                "SELECT value FROM response_headers "
   1172                "WHERE name='vary' COLLATE NOCASE "
   1173                "AND entry_id=:entry_id;"_ns));
   1174 
   1175        QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));
   1176 
   1177        QM_TRY_RETURN((
   1178            quota::CollectElementsWhileHasResultTyped<AutoTArray<nsCString, 8>>(
   1179                *state, [](auto& stmt) {
   1180                  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1181                      nsCString, stmt, GetUTF8String, 0));
   1182                })));
   1183      }()));
   1184 
   1185  // Should not have called this function if this was not the case
   1186  MOZ_DIAGNOSTIC_ASSERT(!varyValues.IsEmpty());
   1187 
   1188  QM_TRY_INSPECT(const auto& state,
   1189                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1190                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1191                     "SELECT name, value FROM request_headers "
   1192                     "WHERE entry_id=:entry_id;"_ns));
   1193 
   1194  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));
   1195 
   1196  RefPtr<InternalHeaders> cachedHeaders =
   1197      new InternalHeaders(HeadersGuardEnum::None);
   1198 
   1199  QM_TRY(quota::CollectWhileHasResult(
   1200      *state, [&cachedHeaders](auto& stmt) -> Result<Ok, nsresult> {
   1201        QM_TRY_INSPECT(const auto& name,
   1202                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   1203                                                         GetUTF8String, 0));
   1204        QM_TRY_INSPECT(const auto& value,
   1205                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
   1206                                                         GetUTF8String, 1));
   1207 
   1208        ErrorResult errorResult;
   1209 
   1210        cachedHeaders->Append(name, value, errorResult);
   1211        if (errorResult.Failed()) {
   1212          return Err(errorResult.StealNSResult());
   1213        }
   1214 
   1215        return Ok{};
   1216      }));
   1217 
   1218  RefPtr<InternalHeaders> queryHeaders =
   1219      TypeUtils::ToInternalHeaders(aRequest.headers());
   1220 
   1221  // Assume the vary headers match until we find a conflict
   1222  bool varyHeadersMatch = true;
   1223 
   1224  for (const auto& varyValue : varyValues) {
   1225    // Extract the header names inside the Vary header value.
   1226    bool bailOut = false;
   1227    for (const nsACString& header :
   1228         nsCCharSeparatedTokenizer(varyValue, NS_HTTP_HEADER_SEP).ToRange()) {
   1229      MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"),
   1230                            "We should have already caught this in "
   1231                            "TypeUtils::ToPCacheResponseWithoutBody()");
   1232 
   1233      ErrorResult errorResult;
   1234      nsAutoCString queryValue;
   1235      queryHeaders->Get(header, queryValue, errorResult);
   1236      if (errorResult.Failed()) {
   1237        errorResult.SuppressException();
   1238        MOZ_DIAGNOSTIC_ASSERT(queryValue.IsEmpty());
   1239      }
   1240 
   1241      nsAutoCString cachedValue;
   1242      cachedHeaders->Get(header, cachedValue, errorResult);
   1243      if (errorResult.Failed()) {
   1244        errorResult.SuppressException();
   1245        MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty());
   1246      }
   1247 
   1248      if (queryValue != cachedValue) {
   1249        varyHeadersMatch = false;
   1250        bailOut = true;
   1251        break;
   1252      }
   1253    }
   1254 
   1255    if (bailOut) {
   1256      break;
   1257    }
   1258  }
   1259 
   1260  return varyHeadersMatch;
   1261 }
   1262 
   1263 static nsresult SelectAndDeleteEntriesInternal(
   1264    mozIStorageConnection& aConn, const Span<const EntryId>& aEntryIdList,
   1265    nsTArray<nsID>& aDeletedBodyIdListOut,
   1266    nsTArray<IdCount>& aDeletedSecurityIdListOut,
   1267    int64_t& aDeletedPaddingSizeOut) {
   1268  nsAutoCString query(
   1269      "SELECT "
   1270      "request_body_id, "
   1271      "response_body_id, "
   1272      "response_security_info_id, "
   1273      "response_padding_size "
   1274      "FROM entries WHERE id IN (");
   1275 
   1276  AppendListParamsToQuery(query, aEntryIdList.Length());
   1277  query.AppendLiteral(")");
   1278 
   1279  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1280                                        nsCOMPtr<mozIStorageStatement>, aConn,
   1281                                        CreateStatement, query));
   1282 
   1283  QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList)));
   1284 
   1285  int64_t overallPaddingSize = 0;
   1286 
   1287  QM_TRY(quota::CollectWhileHasResult(
   1288      *state,
   1289      [&overallPaddingSize, &aDeletedBodyIdListOut,
   1290       &aDeletedSecurityIdListOut](auto& stmt) -> Result<Ok, nsresult> {
   1291        // extract 0 to 2 nsID structs per row
   1292        for (uint32_t i = 0; i < 2; ++i) {
   1293          QM_TRY_INSPECT(const bool& isNull,
   1294                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));
   1295 
   1296          if (!isNull) {
   1297            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));
   1298 
   1299            aDeletedBodyIdListOut.AppendElement(id);
   1300          }
   1301        }
   1302 
   1303        {  // and then a possible third entry for the security id
   1304          QM_TRY_INSPECT(const bool& isNull,
   1305                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2));
   1306 
   1307          if (!isNull) {
   1308            QM_TRY_INSPECT(const int32_t& securityId,
   1309                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2));
   1310 
   1311            // XXXtt: Consider using map for aDeletedSecuityIdListOut.
   1312            auto foundIt =
   1313                std::find_if(aDeletedSecurityIdListOut.begin(),
   1314                             aDeletedSecurityIdListOut.end(),
   1315                             [securityId](const auto& deletedSecurityId) {
   1316                               return deletedSecurityId.mId == securityId;
   1317                             });
   1318 
   1319            if (foundIt == aDeletedSecurityIdListOut.end()) {
   1320              // Add a new entry for this ID with a count of 1, if it's not in
   1321              // the list
   1322              aDeletedSecurityIdListOut.AppendElement(IdCount(securityId));
   1323            } else {
   1324              // Otherwise, increment the count for this ID
   1325              foundIt->mCount += 1;
   1326            }
   1327          }
   1328        }
   1329 
   1330        {
   1331          // It's possible to have null padding size for non-opaque response
   1332          QM_TRY_INSPECT(const bool& isNull,
   1333                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3));
   1334 
   1335          if (!isNull) {
   1336            QM_TRY_INSPECT(const int64_t& paddingSize,
   1337                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3));
   1338 
   1339            MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
   1340            MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - overallPaddingSize >=
   1341                                  paddingSize);
   1342            overallPaddingSize += paddingSize;
   1343          }
   1344        }
   1345 
   1346        return Ok{};
   1347      }));
   1348 
   1349  aDeletedPaddingSizeOut += overallPaddingSize;
   1350 
   1351  // Dependent records removed via ON DELETE CASCADE
   1352 
   1353  query = "DELETE FROM entries WHERE id IN ("_ns;
   1354  AppendListParamsToQuery(query, aEntryIdList.Length());
   1355  query.AppendLiteral(")");
   1356 
   1357  {
   1358    QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1359                                          nsCOMPtr<mozIStorageStatement>, aConn,
   1360                                          CreateStatement, query));
   1361 
   1362    QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList)));
   1363 
   1364    QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1365  }
   1366 
   1367  return NS_OK;
   1368 }
   1369 
   1370 static nsresult DeleteEntriesInternal(
   1371    mozIStorageConnection& aConn, const nsTArray<EntryId>& aEntryIdList,
   1372    nsTArray<nsID>& aDeletedBodyIdListOut,
   1373    nsTArray<IdCount>& aDeletedSecurityIdListOut,
   1374    int64_t& aDeletedPaddingSizeOut, uint32_t aPos, uint32_t aLen) {
   1375  MOZ_ASSERT(!NS_IsMainThread());
   1376 
   1377  if (aEntryIdList.IsEmpty()) {
   1378    return NS_OK;
   1379  }
   1380 
   1381  MOZ_DIAGNOSTIC_ASSERT(aPos < aEntryIdList.Length());
   1382 
   1383  auto remaining = aLen;
   1384  uint32_t currPos = 0;
   1385 
   1386  do {
   1387    // Sqlite limits the number of entries allowed for an IN clause,
   1388    // so split up larger operations.
   1389    auto currLen = std::min(kMaxEntriesPerStatement, remaining);
   1390 
   1391    SelectAndDeleteEntriesInternal(
   1392        aConn, Span<const EntryId>(aEntryIdList.Elements() + currPos, currLen),
   1393        aDeletedBodyIdListOut, aDeletedSecurityIdListOut,
   1394        aDeletedPaddingSizeOut);
   1395 
   1396    remaining -= currLen;
   1397    currPos += currLen;
   1398 
   1399  } while (remaining > 0);
   1400 
   1401  return NS_OK;
   1402 }
   1403 
   1404 Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult>
   1405 DeleteEntries(mozIStorageConnection& aConn,
   1406              const nsTArray<EntryId>& aEntryIdList) {
   1407  auto result =
   1408      std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0});
   1409 
   1410  QM_TRY(MOZ_TO_RESULT(DeleteEntriesInternal(
   1411      aConn, aEntryIdList, std::get<0>(result), std::get<1>(result),
   1412      std::get<2>(result), 0, aEntryIdList.Length())));
   1413 
   1414  return result;
   1415 }
   1416 
   1417 Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult>
   1418 DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId) {
   1419  auto result =
   1420      std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0});
   1421  auto& deletedBodyIdList = std::get<0>(result);
   1422  auto& deletedSecurityIdList = std::get<1>(result);
   1423  auto& deletedPaddingSize = std::get<2>(result);
   1424 
   1425  // XXX: We could create a query string with aggregation that would generate
   1426  // a single summary result such that we don't have to go through each row
   1427  // just to aggregate the result. This method could become much more
   1428  // performant.
   1429  //
   1430  // The columns could look like:
   1431  //
   1432  // GROUP_CONCAT(request_body_id || ',' || response_body_id),
   1433  // GROUP_CONCAT(response_security_info_id),
   1434  // SUM(response_padding_size)
   1435  //
   1436  // strtok the result row to generate the desired output to fill-in
   1437  // deletedBodyIdList, deletedSecurityIdList and deletedPaddingSize
   1438  //
   1439  // I am not sure about the memory requirements for such operation;
   1440  // it will all depend upon the result filtered by the `cache_id`.
   1441  nsAutoCString query(
   1442      "SELECT "
   1443      "request_body_id, "
   1444      "response_body_id, "
   1445      "response_security_info_id, "
   1446      "response_padding_size "
   1447      "FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns);
   1448 
   1449  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1450                                        nsCOMPtr<mozIStorageStatement>, aConn,
   1451                                        CreateStatement, query));
   1452 
   1453  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
   1454 
   1455  QM_TRY(quota::CollectWhileHasResult(
   1456      *state,
   1457      [&deletedPaddingSize, &deletedBodyIdList,
   1458       &deletedSecurityIdList](auto& stmt) -> Result<Ok, nsresult> {
   1459        // extract 0 to 2 nsID structs per row
   1460        for (uint32_t i = 0; i < 2; ++i) {
   1461          QM_TRY_INSPECT(const bool& isNull,
   1462                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));
   1463 
   1464          if (!isNull) {
   1465            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));
   1466 
   1467            deletedBodyIdList.AppendElement(id);
   1468          }
   1469        }
   1470 
   1471        {  // and then a possible third entry for the security id
   1472          QM_TRY_INSPECT(const bool& isNull,
   1473                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2));
   1474 
   1475          if (!isNull) {
   1476            QM_TRY_INSPECT(const int32_t& securityId,
   1477                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2));
   1478 
   1479            // XXXtt: Consider using map for aDeletedSecuityIdListOut.
   1480            auto foundIt = std::find_if(
   1481                deletedSecurityIdList.begin(), deletedSecurityIdList.end(),
   1482                [securityId](const auto& deletedSecurityId) {
   1483                  return deletedSecurityId.mId == securityId;
   1484                });
   1485 
   1486            if (foundIt == deletedSecurityIdList.end()) {
   1487              // Add a new entry for this ID with a count of 1, if it's not in
   1488              // the list
   1489              deletedSecurityIdList.AppendElement(IdCount(securityId));
   1490            } else {
   1491              // Otherwise, increment the count for this ID
   1492              foundIt->mCount += 1;
   1493            }
   1494          }
   1495        }
   1496 
   1497        {
   1498          // It's possible to have null padding size for non-opaque response
   1499          QM_TRY_INSPECT(const bool& isNull,
   1500                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3));
   1501 
   1502          if (!isNull) {
   1503            QM_TRY_INSPECT(const int64_t& paddingSize,
   1504                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3));
   1505 
   1506            MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
   1507 
   1508            // Assert overflow
   1509            MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - deletedPaddingSize >=
   1510                                  paddingSize);
   1511 
   1512            deletedPaddingSize += paddingSize;
   1513          }
   1514        }
   1515 
   1516        return Ok{};
   1517      }));
   1518 
   1519  // Dependent records removed via ON DELETE CASCADE
   1520 
   1521  query = "DELETE FROM entries WHERE cache_id=:cache_id"_ns;
   1522 
   1523  {
   1524    QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1525                                          nsCOMPtr<mozIStorageStatement>, aConn,
   1526                                          CreateStatement, query));
   1527 
   1528    QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
   1529    QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1530  }
   1531 
   1532  return result;
   1533 }
   1534 
   1535 Result<int32_t, nsresult> InsertSecurityInfo(
   1536    mozIStorageConnection& aConn, nsICryptoHash& aCrypto,
   1537    nsITransportSecurityInfo* aSecurityInfo) {
   1538  MOZ_DIAGNOSTIC_ASSERT(aSecurityInfo);
   1539  if (!aSecurityInfo) {
   1540    return Err(NS_ERROR_FAILURE);
   1541  }
   1542  nsCString data;
   1543  nsresult rv = aSecurityInfo->ToString(data);
   1544  if (NS_FAILED(rv)) {
   1545    return Err(rv);
   1546  }
   1547 
   1548  // We want to use an index to find existing security blobs, but indexing
   1549  // the full blob would be quite expensive.  Instead, we index a small
   1550  // hash value.  Calculate this hash as the first 8 bytes of the SHA1 of
   1551  // the full data.
   1552  QM_TRY_INSPECT(const auto& hash, HashCString(aCrypto, data));
   1553 
   1554  // Next, search for an existing entry for this blob by comparing the hash
   1555  // value first and then the full data.  SQLite is smart enough to use
   1556  // the index on the hash to search the table before doing the expensive
   1557  // comparison of the large data column.  (This was verified with EXPLAIN.)
   1558  QM_TRY_INSPECT(
   1559      const auto& selectStmt,
   1560      quota::CreateAndExecuteSingleStepStatement<
   1561          quota::SingleStepResult::ReturnNullIfNoResult>(
   1562          aConn,
   1563          // Note that hash and data are blobs, but we can use = here since the
   1564          // columns are NOT NULL.
   1565          "SELECT id, refcount FROM security_info WHERE hash=:hash AND "
   1566          "data=:data;"_ns,
   1567          [&hash, &data](auto& state) -> Result<Ok, nsresult> {
   1568            QM_TRY(MOZ_TO_RESULT(
   1569                state.BindUTF8StringAsBlobByName("hash"_ns, hash)));
   1570            QM_TRY(MOZ_TO_RESULT(
   1571                state.BindUTF8StringAsBlobByName("data"_ns, data)));
   1572 
   1573            return Ok{};
   1574          }));
   1575 
   1576  // This security info blob is already in the database
   1577  if (selectStmt) {
   1578    // get the existing security blob id to return
   1579    QM_TRY_INSPECT(const int32_t& id,
   1580                   MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 0));
   1581    QM_TRY_INSPECT(const int32_t& refcount,
   1582                   MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 1));
   1583 
   1584    // But first, update the refcount in the database.
   1585    QM_TRY_INSPECT(
   1586        const auto& state,
   1587        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1588            nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1589            "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns));
   1590 
   1591    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, refcount + 1)));
   1592    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, id)));
   1593    QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1594 
   1595    return id;
   1596  }
   1597 
   1598  // This is a new security info blob.  Create a new row in the security table
   1599  // with an initial refcount of 1.
   1600  QM_TRY_INSPECT(const auto& state,
   1601                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1602                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1603                     "INSERT INTO security_info (hash, data, refcount) "
   1604                     "VALUES (:hash, :data, 1);"_ns));
   1605 
   1606  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("hash"_ns, hash)));
   1607  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("data"_ns, data)));
   1608  QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1609 
   1610  {
   1611    QM_TRY_INSPECT(const auto& state,
   1612                   quota::CreateAndExecuteSingleStepStatement(
   1613                       aConn, "SELECT last_insert_rowid()"_ns));
   1614 
   1615    QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   1616  }
   1617 }
   1618 
   1619 nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId,
   1620                            int32_t aCount) {
   1621  // First, we need to determine the current refcount for this security blob.
   1622  QM_TRY_INSPECT(
   1623      const int32_t& refcount, ([&aConn, aId]() -> Result<int32_t, nsresult> {
   1624        QM_TRY_INSPECT(
   1625            const auto& state,
   1626            quota::CreateAndExecuteSingleStepStatement(
   1627                aConn, "SELECT refcount FROM security_info WHERE id=:id;"_ns,
   1628                [aId](auto& state) -> Result<Ok, nsresult> {
   1629                  QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aId)));
   1630                  return Ok{};
   1631                }));
   1632 
   1633        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   1634      }()));
   1635 
   1636  MOZ_ASSERT_DEBUG_OR_FUZZING(refcount >= aCount);
   1637 
   1638  // Next, calculate the new refcount
   1639  int32_t newCount = refcount - aCount;
   1640 
   1641  // If the last reference to this security blob was removed we can
   1642  // just remove the entire row.
   1643  if (newCount == 0) {
   1644    QM_TRY_INSPECT(const auto& state,
   1645                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1646                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1647                       "DELETE FROM security_info WHERE id=:id;"_ns));
   1648 
   1649    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId)));
   1650    QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1651 
   1652    return NS_OK;
   1653  }
   1654 
   1655  // Otherwise update the refcount in the table to reflect the reduced
   1656  // number of references to the security blob.
   1657  QM_TRY_INSPECT(
   1658      const auto& state,
   1659      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1660          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1661          "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns));
   1662 
   1663  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, newCount)));
   1664  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId)));
   1665  QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1666 
   1667  return NS_OK;
   1668 }
   1669 
   1670 nsresult DeleteSecurityInfoList(
   1671    mozIStorageConnection& aConn,
   1672    const nsTArray<IdCount>& aDeletedStorageIdList) {
   1673  for (const auto& deletedStorageId : aDeletedStorageIdList) {
   1674    QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfo(aConn, deletedStorageId.mId,
   1675                                            deletedStorageId.mCount)));
   1676  }
   1677 
   1678  return NS_OK;
   1679 }
   1680 
   1681 nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId,
   1682                     const CacheRequest& aRequest, const nsID* aRequestBodyId,
   1683                     const CacheResponse& aResponse,
   1684                     const nsID* aResponseBodyId) {
   1685  MOZ_ASSERT(!NS_IsMainThread());
   1686 
   1687  QM_TRY_INSPECT(const auto& crypto,
   1688                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>,
   1689                                         MOZ_SELECT_OVERLOAD(do_CreateInstance),
   1690                                         NS_CRYPTO_HASH_CONTRACTID));
   1691 
   1692  int32_t securityId = -1;
   1693  if (aResponse.securityInfo()) {
   1694    QM_TRY_UNWRAP(securityId,
   1695                  InsertSecurityInfo(aConn, *crypto, aResponse.securityInfo()));
   1696  }
   1697 
   1698  {
   1699    QM_TRY_INSPECT(const auto& state,
   1700                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1701                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1702                       "INSERT INTO entries ("
   1703                       "request_method, "
   1704                       "request_url_no_query, "
   1705                       "request_url_no_query_hash, "
   1706                       "request_url_query, "
   1707                       "request_url_query_hash, "
   1708                       "request_url_fragment, "
   1709                       "request_referrer, "
   1710                       "request_referrer_policy, "
   1711                       "request_headers_guard, "
   1712                       "request_mode, "
   1713                       "request_credentials, "
   1714                       "request_contentpolicytype, "
   1715                       "request_cache, "
   1716                       "request_redirect, "
   1717                       "request_integrity, "
   1718                       "request_body_id, "
   1719                       "request_body_disk_size, "
   1720                       "response_type, "
   1721                       "response_status, "
   1722                       "response_status_text, "
   1723                       "response_headers_guard, "
   1724                       "response_body_id, "
   1725                       "response_body_disk_size, "
   1726                       "response_security_info_id, "
   1727                       "response_principal_info, "
   1728                       "response_padding_size, "
   1729                       "cache_id "
   1730                       ") VALUES ("
   1731                       ":request_method, "
   1732                       ":request_url_no_query, "
   1733                       ":request_url_no_query_hash, "
   1734                       ":request_url_query, "
   1735                       ":request_url_query_hash, "
   1736                       ":request_url_fragment, "
   1737                       ":request_referrer, "
   1738                       ":request_referrer_policy, "
   1739                       ":request_headers_guard, "
   1740                       ":request_mode, "
   1741                       ":request_credentials, "
   1742                       ":request_contentpolicytype, "
   1743                       ":request_cache, "
   1744                       ":request_redirect, "
   1745                       ":request_integrity, "
   1746                       ":request_body_id, "
   1747                       ":request_body_disk_size, "
   1748                       ":response_type, "
   1749                       ":response_status, "
   1750                       ":response_status_text, "
   1751                       ":response_headers_guard, "
   1752                       ":response_body_id, "
   1753                       ":response_body_disk_size, "
   1754                       ":response_security_info_id, "
   1755                       ":response_principal_info, "
   1756                       ":response_padding_size, "
   1757                       ":cache_id "
   1758                       ");"_ns));
   1759 
   1760    QM_TRY(MOZ_TO_RESULT(
   1761        state->BindUTF8StringByName("request_method"_ns, aRequest.method())));
   1762 
   1763    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName(
   1764        "request_url_no_query"_ns, aRequest.urlWithoutQuery())));
   1765 
   1766    QM_TRY_INSPECT(const auto& urlWithoutQueryHash,
   1767                   HashCString(*crypto, aRequest.urlWithoutQuery()));
   1768 
   1769    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName(
   1770        "request_url_no_query_hash"_ns, urlWithoutQueryHash)));
   1771 
   1772    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_url_query"_ns,
   1773                                                     aRequest.urlQuery())));
   1774 
   1775    QM_TRY_INSPECT(const auto& urlQueryHash,
   1776                   HashCString(*crypto, aRequest.urlQuery()));
   1777 
   1778    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName(
   1779        "request_url_query_hash"_ns, urlQueryHash)));
   1780 
   1781    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_url_fragment"_ns,
   1782                                                     aRequest.urlFragment())));
   1783 
   1784    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_referrer"_ns,
   1785                                                     aRequest.referrer())));
   1786 
   1787    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1788        "request_referrer_policy"_ns,
   1789        static_cast<int32_t>(aRequest.referrerPolicy()))));
   1790 
   1791    QM_TRY(MOZ_TO_RESULT(
   1792        state->BindInt32ByName("request_headers_guard"_ns,
   1793                               static_cast<int32_t>(aRequest.headersGuard()))));
   1794 
   1795    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1796        "request_mode"_ns, static_cast<int32_t>(aRequest.mode()))));
   1797 
   1798    QM_TRY(MOZ_TO_RESULT(
   1799        state->BindInt32ByName("request_credentials"_ns,
   1800                               static_cast<int32_t>(aRequest.credentials()))));
   1801 
   1802    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1803        "request_contentpolicytype"_ns,
   1804        static_cast<int32_t>(aRequest.contentPolicyType()))));
   1805 
   1806    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1807        "request_cache"_ns, static_cast<int32_t>(aRequest.requestCache()))));
   1808 
   1809    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1810        "request_redirect"_ns,
   1811        static_cast<int32_t>(aRequest.requestRedirect()))));
   1812 
   1813    QM_TRY(MOZ_TO_RESULT(
   1814        state->BindStringByName("request_integrity"_ns, aRequest.integrity())));
   1815 
   1816    QM_TRY(MOZ_TO_RESULT(BindId(*state, "request_body_id"_ns, aRequestBodyId)));
   1817 
   1818    QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("request_body_disk_size"_ns,
   1819                                                aRequest.bodyDiskSize())));
   1820 
   1821    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1822        "response_type"_ns, static_cast<int32_t>(aResponse.type()))));
   1823 
   1824    QM_TRY(MOZ_TO_RESULT(
   1825        state->BindInt32ByName("response_status"_ns, aResponse.status())));
   1826 
   1827    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("response_status_text"_ns,
   1828                                                     aResponse.statusText())));
   1829 
   1830    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName(
   1831        "response_headers_guard"_ns,
   1832        static_cast<int32_t>(aResponse.headersGuard()))));
   1833 
   1834    QM_TRY(
   1835        MOZ_TO_RESULT(BindId(*state, "response_body_id"_ns, aResponseBodyId)));
   1836 
   1837    QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("response_body_disk_size"_ns,
   1838                                                aResponse.bodyDiskSize())));
   1839 
   1840    if (!aResponse.securityInfo()) {
   1841      QM_TRY(
   1842          MOZ_TO_RESULT(state->BindNullByName("response_security_info_id"_ns)));
   1843    } else {
   1844      QM_TRY(MOZ_TO_RESULT(
   1845          state->BindInt32ByName("response_security_info_id"_ns, securityId)));
   1846    }
   1847 
   1848    nsAutoCString serializedInfo;
   1849    // We only allow content serviceworkers right now.
   1850    if (aResponse.principalInfo().isSome()) {
   1851      const mozilla::ipc::PrincipalInfo& principalInfo =
   1852          aResponse.principalInfo().ref();
   1853      MOZ_DIAGNOSTIC_ASSERT(principalInfo.type() ==
   1854                            mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
   1855      const mozilla::ipc::ContentPrincipalInfo& cInfo =
   1856          principalInfo.get_ContentPrincipalInfo();
   1857 
   1858      serializedInfo.Append(cInfo.spec());
   1859 
   1860      nsAutoCString suffix;
   1861      cInfo.attrs().CreateSuffix(suffix);
   1862      serializedInfo.Append(suffix);
   1863    }
   1864 
   1865    QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName(
   1866        "response_principal_info"_ns, serializedInfo)));
   1867 
   1868    if (aResponse.paddingSize() == InternalResponse::UNKNOWN_PADDING_SIZE) {
   1869      MOZ_DIAGNOSTIC_ASSERT(aResponse.type() != ResponseType::Opaque);
   1870      QM_TRY(MOZ_TO_RESULT(state->BindNullByName("response_padding_size"_ns)));
   1871    } else {
   1872      MOZ_DIAGNOSTIC_ASSERT(aResponse.paddingSize() >= 0);
   1873      MOZ_DIAGNOSTIC_ASSERT(aResponse.type() == ResponseType::Opaque);
   1874 
   1875      QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("response_padding_size"_ns,
   1876                                                  aResponse.paddingSize())));
   1877    }
   1878 
   1879    QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
   1880 
   1881    QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1882  }
   1883 
   1884  QM_TRY_INSPECT(
   1885      const int32_t& entryId, ([&aConn]() -> Result<int32_t, nsresult> {
   1886        QM_TRY_INSPECT(const auto& state,
   1887                       quota::CreateAndExecuteSingleStepStatement(
   1888                           aConn, "SELECT last_insert_rowid()"_ns));
   1889 
   1890        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   1891      }()));
   1892 
   1893  {
   1894    QM_TRY_INSPECT(const auto& state,
   1895                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1896                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1897                       "INSERT INTO request_headers ("
   1898                       "name, "
   1899                       "value, "
   1900                       "entry_id "
   1901                       ") VALUES (:name, :value, :entry_id)"_ns));
   1902 
   1903    for (const auto& requestHeader : aRequest.headers()) {
   1904      QM_TRY(MOZ_TO_RESULT(
   1905          state->BindUTF8StringByName("name"_ns, requestHeader.name())));
   1906 
   1907      QM_TRY(MOZ_TO_RESULT(
   1908          state->BindUTF8StringByName("value"_ns, requestHeader.value())));
   1909 
   1910      QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));
   1911 
   1912      QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1913    }
   1914  }
   1915 
   1916  {
   1917    QM_TRY_INSPECT(const auto& state,
   1918                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1919                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1920                       "INSERT INTO response_headers ("
   1921                       "name, "
   1922                       "value, "
   1923                       "entry_id "
   1924                       ") VALUES (:name, :value, :entry_id)"_ns));
   1925 
   1926    for (const auto& responseHeader : aResponse.headers()) {
   1927      QM_TRY(MOZ_TO_RESULT(
   1928          state->BindUTF8StringByName("name"_ns, responseHeader.name())));
   1929      QM_TRY(MOZ_TO_RESULT(
   1930          state->BindUTF8StringByName("value"_ns, responseHeader.value())));
   1931      QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));
   1932      QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1933    }
   1934  }
   1935 
   1936  {
   1937    QM_TRY_INSPECT(const auto& state,
   1938                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1939                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   1940                       "INSERT INTO response_url_list ("
   1941                       "url, "
   1942                       "entry_id "
   1943                       ") VALUES (:url, :entry_id)"_ns));
   1944 
   1945    for (const auto& responseUrl : aResponse.urlList()) {
   1946      QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("url"_ns, responseUrl)));
   1947      QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));
   1948      QM_TRY(MOZ_TO_RESULT(state->Execute()));
   1949    }
   1950  }
   1951 
   1952  return NS_OK;
   1953 }
   1954 
   1955 /**
   1956 * Gets a HeadersEntry from a storage statement by retrieving the first column
   1957 * as the name and the second column as the value.
   1958 */
   1959 Result<HeadersEntry, nsresult> GetHeadersEntryFromStatement(
   1960    mozIStorageStatement& aStmt) {
   1961  HeadersEntry header;
   1962 
   1963  QM_TRY_UNWRAP(header.name(), MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1964                                   nsCString, aStmt, GetUTF8String, 0));
   1965  QM_TRY_UNWRAP(header.value(), MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   1966                                    nsCString, aStmt, GetUTF8String, 1));
   1967 
   1968  return header;
   1969 }
   1970 
   1971 Result<SavedResponse, nsresult> ReadResponse(mozIStorageConnection& aConn,
   1972                                             EntryId aEntryId) {
   1973  MOZ_ASSERT(!NS_IsMainThread());
   1974 
   1975  SavedResponse savedResponse;
   1976 
   1977  QM_TRY_INSPECT(
   1978      const auto& state,
   1979      quota::CreateAndExecuteSingleStepStatement(
   1980          aConn,
   1981          "SELECT "
   1982          "entries.response_type, "
   1983          "entries.response_status, "
   1984          "entries.response_status_text, "
   1985          "entries.response_headers_guard, "
   1986          "entries.response_body_id, "
   1987          "entries.response_principal_info, "
   1988          "entries.response_padding_size, "
   1989          "security_info.data, "
   1990          "entries.request_credentials "
   1991          "FROM entries "
   1992          "LEFT OUTER JOIN security_info "
   1993          "ON entries.response_security_info_id=security_info.id "
   1994          "WHERE entries.id=:id;"_ns,
   1995          [aEntryId](auto& state) -> Result<Ok, nsresult> {
   1996            QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aEntryId)));
   1997 
   1998            return Ok{};
   1999          }));
   2000 
   2001  QM_TRY_INSPECT(const int32_t& type,
   2002                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   2003  savedResponse.mValue.type() = static_cast<ResponseType>(type);
   2004 
   2005  QM_TRY_INSPECT(const int32_t& status,
   2006                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 1));
   2007  savedResponse.mValue.status() = static_cast<uint32_t>(status);
   2008 
   2009  QM_TRY(MOZ_TO_RESULT(
   2010      state->GetUTF8String(2, savedResponse.mValue.statusText())));
   2011 
   2012  QM_TRY_INSPECT(const int32_t& guard,
   2013                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 3));
   2014  savedResponse.mValue.headersGuard() = static_cast<HeadersGuardEnum>(guard);
   2015 
   2016  QM_TRY_INSPECT(const bool& nullBody,
   2017                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetIsNull, 4));
   2018  savedResponse.mHasBodyId = !nullBody;
   2019 
   2020  if (savedResponse.mHasBodyId) {
   2021    QM_TRY_UNWRAP(savedResponse.mBodyId, ExtractId(*state, 4));
   2022  }
   2023 
   2024  QM_TRY_INSPECT(const auto& serializedInfo,
   2025                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *state,
   2026                                                   GetUTF8String, 5));
   2027 
   2028  savedResponse.mValue.principalInfo() = Nothing();
   2029  if (!serializedInfo.IsEmpty()) {
   2030    nsAutoCString specNoSuffix;
   2031    OriginAttributes attrs;
   2032    if (!attrs.PopulateFromOrigin(serializedInfo, specNoSuffix)) {
   2033      NS_WARNING("Something went wrong parsing a serialized principal!");
   2034      return Err(NS_ERROR_FAILURE);
   2035    }
   2036 
   2037    nsCOMPtr<nsIURI> url;
   2038    QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(url), specNoSuffix)));
   2039 
   2040 #ifdef DEBUG
   2041    nsAutoCString scheme;
   2042    QM_TRY(MOZ_TO_RESULT(url->GetScheme(scheme)));
   2043 
   2044    MOZ_ASSERT(
   2045        scheme == "http" || scheme == "https" || scheme == "file" ||
   2046        // A cached response entry may have a moz-extension principal if:
   2047        //
   2048        // - This is an extension background service worker. The response for
   2049        //   the main script is expected tobe a moz-extension content principal
   2050        //   (the pref "extensions.backgroundServiceWorker.enabled" must be
   2051        //   enabled, if the pref is toggled to false at runtime then any
   2052        //   service worker registered for a moz-extension principal will be
   2053        //   unregistered on the next startup).
   2054        //
   2055        // - An extension is redirecting a script being imported info a worker
   2056        //   created from a regular webpage to a web-accessible extension
   2057        //   script. The reponse for these redirects will have a moz-extension
   2058        //   principal. Although extensions can attempt to redirect the main
   2059        //   script of service workers, this will always cause the install
   2060        //   process to fail.
   2061        scheme == "moz-extension");
   2062 #endif
   2063 
   2064    nsCOMPtr<nsIPrincipal> principal =
   2065        BasePrincipal::CreateContentPrincipal(url, attrs);
   2066    if (!principal) {
   2067      return Err(NS_ERROR_NULL_POINTER);
   2068    }
   2069 
   2070    nsCString origin;
   2071    QM_TRY(MOZ_TO_RESULT(principal->GetOriginNoSuffix(origin)));
   2072 
   2073    nsCString baseDomain;
   2074    QM_TRY(MOZ_TO_RESULT(principal->GetBaseDomain(baseDomain)));
   2075 
   2076    savedResponse.mValue.principalInfo() =
   2077        Some(mozilla::ipc::ContentPrincipalInfo(attrs, origin, specNoSuffix,
   2078                                                Nothing(), baseDomain));
   2079  }
   2080 
   2081  QM_TRY_INSPECT(const bool& nullPadding,
   2082                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetIsNull, 6));
   2083 
   2084  if (nullPadding) {
   2085    MOZ_DIAGNOSTIC_ASSERT(savedResponse.mValue.type() != ResponseType::Opaque);
   2086    savedResponse.mValue.paddingSize() = InternalResponse::UNKNOWN_PADDING_SIZE;
   2087  } else {
   2088    MOZ_DIAGNOSTIC_ASSERT(savedResponse.mValue.type() == ResponseType::Opaque);
   2089    QM_TRY_INSPECT(const int64_t& paddingSize,
   2090                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 6));
   2091 
   2092    MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
   2093    savedResponse.mValue.paddingSize() = paddingSize;
   2094  }
   2095 
   2096  nsCString data;
   2097  QM_TRY(MOZ_TO_RESULT(state->GetBlobAsUTF8String(7, data)));
   2098  if (!data.IsEmpty()) {
   2099    nsCOMPtr<nsITransportSecurityInfo> securityInfo;
   2100    nsresult rv = mozilla::psm::TransportSecurityInfo::Read(
   2101        data, getter_AddRefs(securityInfo));
   2102    if (NS_FAILED(rv)) {
   2103      return Err(rv);
   2104    }
   2105    if (!securityInfo) {
   2106      return Err(NS_ERROR_FAILURE);
   2107    }
   2108    savedResponse.mValue.securityInfo() = securityInfo.forget();
   2109  }
   2110 
   2111  QM_TRY_INSPECT(const int32_t& credentials,
   2112                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 8));
   2113  savedResponse.mValue.credentials() =
   2114      static_cast<RequestCredentials>(credentials);
   2115 
   2116  {
   2117    QM_TRY_INSPECT(const auto& state,
   2118                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2119                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2120                       "SELECT "
   2121                       "name, "
   2122                       "value "
   2123                       "FROM response_headers "
   2124                       "WHERE entry_id=:entry_id;"_ns));
   2125 
   2126    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId)));
   2127 
   2128    QM_TRY_UNWRAP(savedResponse.mValue.headers(),
   2129                  quota::CollectElementsWhileHasResult(
   2130                      *state, GetHeadersEntryFromStatement));
   2131  }
   2132 
   2133  {
   2134    QM_TRY_INSPECT(const auto& state,
   2135                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2136                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2137                       "SELECT "
   2138                       "url "
   2139                       "FROM response_url_list "
   2140                       "WHERE entry_id=:entry_id;"_ns));
   2141 
   2142    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId)));
   2143 
   2144    QM_TRY_UNWRAP(savedResponse.mValue.urlList(),
   2145                  quota::CollectElementsWhileHasResult(
   2146                      *state, [](auto& stmt) -> Result<nsCString, nsresult> {
   2147                        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2148                            nsCString, stmt, GetUTF8String, 0));
   2149                      }));
   2150  }
   2151 
   2152  return savedResponse;
   2153 }
   2154 
   2155 Result<SavedRequest, nsresult> ReadRequest(mozIStorageConnection& aConn,
   2156                                           EntryId aEntryId) {
   2157  MOZ_ASSERT(!NS_IsMainThread());
   2158 
   2159  SavedRequest savedRequest;
   2160 
   2161  QM_TRY_INSPECT(
   2162      const auto& state,
   2163      quota::CreateAndExecuteSingleStepStatement<
   2164          quota::SingleStepResult::ReturnNullIfNoResult>(
   2165          aConn,
   2166          "SELECT "
   2167          "request_method, "
   2168          "request_url_no_query, "
   2169          "request_url_query, "
   2170          "request_url_fragment, "
   2171          "request_referrer, "
   2172          "request_referrer_policy, "
   2173          "request_headers_guard, "
   2174          "request_mode, "
   2175          "request_credentials, "
   2176          "request_contentpolicytype, "
   2177          "request_cache, "
   2178          "request_redirect, "
   2179          "request_integrity, "
   2180          "request_body_id "
   2181          "FROM entries "
   2182          "WHERE id=:id;"_ns,
   2183          [aEntryId](auto& state) -> Result<Ok, nsresult> {
   2184            QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aEntryId)));
   2185 
   2186            return Ok{};
   2187          }));
   2188 
   2189  QM_TRY(OkIf(state), Err(NS_ERROR_UNEXPECTED));
   2190 
   2191  QM_TRY(MOZ_TO_RESULT(state->GetUTF8String(0, savedRequest.mValue.method())));
   2192  QM_TRY(MOZ_TO_RESULT(
   2193      state->GetUTF8String(1, savedRequest.mValue.urlWithoutQuery())));
   2194  QM_TRY(
   2195      MOZ_TO_RESULT(state->GetUTF8String(2, savedRequest.mValue.urlQuery())));
   2196  QM_TRY(MOZ_TO_RESULT(
   2197      state->GetUTF8String(3, savedRequest.mValue.urlFragment())));
   2198  QM_TRY(
   2199      MOZ_TO_RESULT(state->GetUTF8String(4, savedRequest.mValue.referrer())));
   2200 
   2201  QM_TRY_INSPECT(const int32_t& referrerPolicy,
   2202                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 5));
   2203  savedRequest.mValue.referrerPolicy() =
   2204      static_cast<ReferrerPolicy>(referrerPolicy);
   2205 
   2206  QM_TRY_INSPECT(const int32_t& guard,
   2207                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 6));
   2208  savedRequest.mValue.headersGuard() = static_cast<HeadersGuardEnum>(guard);
   2209 
   2210  QM_TRY_INSPECT(const int32_t& mode,
   2211                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 7));
   2212  savedRequest.mValue.mode() = static_cast<RequestMode>(mode);
   2213 
   2214  QM_TRY_INSPECT(const int32_t& credentials,
   2215                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 8));
   2216  savedRequest.mValue.credentials() =
   2217      static_cast<RequestCredentials>(credentials);
   2218 
   2219  QM_TRY_INSPECT(const int32_t& requestContentPolicyType,
   2220                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 9));
   2221  savedRequest.mValue.contentPolicyType() =
   2222      static_cast<nsContentPolicyType>(requestContentPolicyType);
   2223 
   2224  QM_TRY_INSPECT(const int32_t& requestCache,
   2225                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 10));
   2226  savedRequest.mValue.requestCache() = static_cast<RequestCache>(requestCache);
   2227 
   2228  QM_TRY_INSPECT(const int32_t& requestRedirect,
   2229                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 11));
   2230  savedRequest.mValue.requestRedirect() =
   2231      static_cast<RequestRedirect>(requestRedirect);
   2232 
   2233  QM_TRY(MOZ_TO_RESULT(state->GetString(12, savedRequest.mValue.integrity())));
   2234 
   2235  QM_TRY_INSPECT(const bool& nullBody,
   2236                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetIsNull, 13));
   2237  savedRequest.mHasBodyId = !nullBody;
   2238  if (savedRequest.mHasBodyId) {
   2239    QM_TRY_UNWRAP(savedRequest.mBodyId, ExtractId(*state, 13));
   2240  }
   2241 
   2242  {
   2243    QM_TRY_INSPECT(const auto& state,
   2244                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2245                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2246                       "SELECT "
   2247                       "name, "
   2248                       "value "
   2249                       "FROM request_headers "
   2250                       "WHERE entry_id=:entry_id;"_ns));
   2251 
   2252    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId)));
   2253 
   2254    QM_TRY_UNWRAP(savedRequest.mValue.headers(),
   2255                  quota::CollectElementsWhileHasResult(
   2256                      *state, GetHeadersEntryFromStatement));
   2257  }
   2258 
   2259  return savedRequest;
   2260 }
   2261 
   2262 void AppendListParamsToQuery(nsACString& aQuery, size_t aLen) {
   2263  MOZ_ASSERT(!NS_IsMainThread());
   2264 
   2265  aQuery.AppendLiteral("?");
   2266  for (size_t i = 1; i < aLen; ++i) {
   2267    aQuery.AppendLiteral(",?");
   2268  }
   2269 }
   2270 
   2271 nsresult BindListParamsToQuery(mozIStorageStatement& aState,
   2272                               const Span<const EntryId>& aEntryIdList) {
   2273  MOZ_ASSERT(!NS_IsMainThread());
   2274  for (size_t i = 0, n = aEntryIdList.Length(); i < n; ++i) {
   2275    QM_TRY(MOZ_TO_RESULT(aState.BindInt32ByIndex(i, aEntryIdList[i])));
   2276  }
   2277  return NS_OK;
   2278 }
   2279 
   2280 nsresult BindId(mozIStorageStatement& aState, const nsACString& aName,
   2281                const nsID* aId) {
   2282  MOZ_ASSERT(!NS_IsMainThread());
   2283 
   2284  if (!aId) {
   2285    QM_TRY(MOZ_TO_RESULT(aState.BindNullByName(aName)));
   2286    return NS_OK;
   2287  }
   2288 
   2289  char idBuf[NSID_LENGTH];
   2290  aId->ToProvidedString(idBuf);
   2291  QM_TRY(MOZ_TO_RESULT(
   2292      aState.BindUTF8StringByName(aName, nsDependentCString(idBuf))));
   2293 
   2294  return NS_OK;
   2295 }
   2296 
   2297 Result<nsID, nsresult> ExtractId(mozIStorageStatement& aState, uint32_t aPos) {
   2298  MOZ_ASSERT(!NS_IsMainThread());
   2299 
   2300  QM_TRY_INSPECT(const auto& idString,
   2301                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, aState,
   2302                                                   GetUTF8String, aPos));
   2303 
   2304  nsID id;
   2305  QM_TRY(OkIf(id.Parse(idString.get())), Err(NS_ERROR_UNEXPECTED));
   2306 
   2307  return id;
   2308 }
   2309 
   2310 Result<NotNull<nsCOMPtr<mozIStorageStatement>>, nsresult>
   2311 CreateAndBindKeyStatement(mozIStorageConnection& aConn,
   2312                          const char* const aQueryFormat,
   2313                          const nsAString& aKey) {
   2314  MOZ_DIAGNOSTIC_ASSERT(aQueryFormat);
   2315 
   2316  // The key is stored as a blob to avoid encoding issues.  An empty string
   2317  // is mapped to NULL for blobs.  Normally we would just write the query
   2318  // as "key IS :key" to do the proper NULL checking, but that prevents
   2319  // sqlite from using the key index.  Therefore use "IS NULL" explicitly
   2320  // if the key is empty, otherwise use "=:key" so that sqlite uses the
   2321  // index.
   2322 
   2323  QM_TRY_UNWRAP(
   2324      auto state,
   2325      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2326          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2327          nsPrintfCString(aQueryFormat,
   2328                          aKey.IsEmpty() ? "key IS NULL" : "key=:key")));
   2329 
   2330  if (!aKey.IsEmpty()) {
   2331    QM_TRY(MOZ_TO_RESULT(state->BindStringAsBlobByName("key"_ns, aKey)));
   2332  }
   2333 
   2334  return WrapNotNull(std::move(state));
   2335 }
   2336 
   2337 Result<nsAutoCString, nsresult> HashCString(nsICryptoHash& aCrypto,
   2338                                            const nsACString& aIn) {
   2339  QM_TRY(MOZ_TO_RESULT(aCrypto.Init(nsICryptoHash::SHA1)));
   2340 
   2341  QM_TRY(MOZ_TO_RESULT(aCrypto.Update(
   2342      reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length())));
   2343 
   2344  nsAutoCString fullHash;
   2345  QM_TRY(MOZ_TO_RESULT(aCrypto.Finish(false /* based64 result */, fullHash)));
   2346 
   2347  return Result<nsAutoCString, nsresult>{std::in_place,
   2348                                         Substring(fullHash, 0, 8)};
   2349 }
   2350 
   2351 }  // namespace
   2352 
   2353 nsresult IncrementalVacuum(mozIStorageConnection& aConn) {
   2354  // Determine how much free space is in the database.
   2355  QM_TRY_INSPECT(const auto& state, quota::CreateAndExecuteSingleStepStatement(
   2356                                        aConn, "PRAGMA freelist_count;"_ns));
   2357 
   2358  QM_TRY_INSPECT(const int32_t& freePages,
   2359                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   2360 
   2361  // We have a relatively small page size, so we want to be careful to avoid
   2362  // fragmentation.  We already use a growth incremental which will cause
   2363  // sqlite to allocate and release multiple pages at the same time.  We can
   2364  // further reduce fragmentation by making our allocated chunks a bit
   2365  // "sticky".  This is done by creating some hysteresis where we allocate
   2366  // pages/chunks as soon as we need them, but we only release pages/chunks
   2367  // when we have a large amount of free space.  This helps with the case
   2368  // where a page is adding and remove resources causing it to dip back and
   2369  // forth across a chunk boundary.
   2370  //
   2371  // So only proceed with releasing pages if we have more than our constant
   2372  // threshold.
   2373  if (freePages <= kMaxFreePages) {
   2374    return NS_OK;
   2375  }
   2376 
   2377  // Release the excess pages back to the sqlite VFS.  This may also release
   2378  // chunks of multiple pages back to the OS.
   2379  const int32_t pagesToRelease = freePages - kMaxFreePages;
   2380 
   2381  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2382      nsPrintfCString("PRAGMA incremental_vacuum(%d);", pagesToRelease))));
   2383 
   2384  // Verify that our incremental vacuum actually did something
   2385 #ifdef DEBUG
   2386  {
   2387    QM_TRY_INSPECT(const auto& state,
   2388                   quota::CreateAndExecuteSingleStepStatement(
   2389                       aConn, "PRAGMA freelist_count;"_ns));
   2390 
   2391    QM_TRY_INSPECT(const int32_t& freePages,
   2392                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
   2393 
   2394    MOZ_ASSERT(freePages <= kMaxFreePages);
   2395  }
   2396 #endif
   2397 
   2398  return NS_OK;
   2399 }
   2400 
   2401 namespace {
   2402 
   2403 // Wrapper around mozIStorageConnection::GetSchemaVersion() that compensates
   2404 // for hacky downgrade schema version tricks.  See the block comments for
   2405 // kHackyDowngradeSchemaVersion and kHackyPaddingSizePresentVersion.
   2406 Result<int32_t, nsresult> GetEffectiveSchemaVersion(
   2407    mozIStorageConnection& aConn) {
   2408  QM_TRY_INSPECT(const int32_t& schemaVersion,
   2409                 MOZ_TO_RESULT_INVOKE_MEMBER(aConn, GetSchemaVersion));
   2410 
   2411  if (schemaVersion == kHackyDowngradeSchemaVersion) {
   2412    // This is the special case.  Check for the existence of the
   2413    // "response_padding_size" colum in table "entries".
   2414    //
   2415    // (pragma_table_info is a table-valued function format variant of
   2416    // "PRAGMA table_info" supported since SQLite 3.16.0.  Firefox 53 shipped
   2417    // was the first release with this functionality, shipping 3.16.2.)
   2418    //
   2419    // If there are any result rows, then the column is present.
   2420    QM_TRY_INSPECT(const bool& hasColumn,
   2421                   quota::CreateAndExecuteSingleStepStatement<
   2422                       quota::SingleStepResult::ReturnNullIfNoResult>(
   2423                       aConn,
   2424                       "SELECT name FROM pragma_table_info('entries') WHERE "
   2425                       "name = 'response_padding_size'"_ns));
   2426 
   2427    if (hasColumn) {
   2428      return kHackyPaddingSizePresentVersion;
   2429    }
   2430  }
   2431 
   2432  return schemaVersion;
   2433 }
   2434 
   2435 #ifdef DEBUG
   2436 struct Expect {
   2437  // Expect exact SQL
   2438  Expect(const char* aName, const char* aType, const char* aSql)
   2439      : mName(aName), mType(aType), mSql(aSql), mIgnoreSql(false) {}
   2440 
   2441  // Ignore SQL
   2442  Expect(const char* aName, const char* aType)
   2443      : mName(aName), mType(aType), mIgnoreSql(true) {}
   2444 
   2445  const nsCString mName;
   2446  const nsCString mType;
   2447  const nsCString mSql;
   2448  const bool mIgnoreSql;
   2449 };
   2450 #endif
   2451 
   2452 nsresult Validate(mozIStorageConnection& aConn) {
   2453  QM_TRY_INSPECT(const int32_t& schemaVersion,
   2454                 GetEffectiveSchemaVersion(aConn));
   2455  QM_TRY(OkIf(schemaVersion == kLatestSchemaVersion), NS_ERROR_FAILURE);
   2456 
   2457 #ifdef DEBUG
   2458  // This is the schema we expect the database at the latest version to
   2459  // contain.  Update this list if you add a new table or index.
   2460  const Expect expects[] = {
   2461      Expect("caches", "table", kTableCaches),
   2462      Expect("sqlite_sequence", "table"),  // auto-gen by sqlite
   2463      Expect("security_info", "table", kTableSecurityInfo),
   2464      Expect("security_info_hash_index", "index", kIndexSecurityInfoHash),
   2465      Expect("entries", "table", kTableEntries),
   2466      Expect("entries_request_match_index", "index", kIndexEntriesRequest),
   2467      Expect("request_headers", "table", kTableRequestHeaders),
   2468      Expect("response_headers", "table", kTableResponseHeaders),
   2469      Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
   2470      Expect("response_url_list", "table", kTableResponseUrlList),
   2471      Expect("storage", "table", kTableStorage),
   2472      Expect("sqlite_autoindex_storage_1", "index"),  // auto-gen by sqlite
   2473      Expect("usage_info", "table", kTableUsageInfo),
   2474      Expect("entries_insert_trigger", "trigger", kTriggerEntriesInsert),
   2475      Expect("entries_update_trigger", "trigger", kTriggerEntriesUpdate),
   2476      Expect("entries_delete_trigger", "trigger", kTriggerEntriesDelete),
   2477  };
   2478 
   2479  // Read the schema from the sqlite_master table and compare.
   2480  QM_TRY_INSPECT(const auto& state,
   2481                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2482                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2483                     "SELECT name, type, sql FROM sqlite_master;"_ns));
   2484 
   2485  QM_TRY(quota::CollectWhileHasResult(
   2486      *state, [&expects](auto& stmt) -> Result<Ok, nsresult> {
   2487        QM_TRY_INSPECT(const auto& name,
   2488                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt,
   2489                                                         GetUTF8String, 0));
   2490        QM_TRY_INSPECT(const auto& type,
   2491                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt,
   2492                                                         GetUTF8String, 1));
   2493        QM_TRY_INSPECT(const auto& sql,
   2494                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt,
   2495                                                         GetUTF8String, 2));
   2496 
   2497        bool foundMatch = false;
   2498        for (const auto& expect : expects) {
   2499          if (name == expect.mName) {
   2500            if (type != expect.mType) {
   2501              NS_WARNING(
   2502                  nsPrintfCString("Unexpected type for Cache schema entry %s",
   2503                                  name.get())
   2504                      .get());
   2505              return Err(NS_ERROR_FAILURE);
   2506            }
   2507 
   2508            if (!expect.mIgnoreSql && sql != expect.mSql) {
   2509              NS_WARNING(
   2510                  nsPrintfCString("Unexpected SQL for Cache schema entry %s",
   2511                                  name.get())
   2512                      .get());
   2513              return Err(NS_ERROR_FAILURE);
   2514            }
   2515 
   2516            foundMatch = true;
   2517            break;
   2518          }
   2519        }
   2520 
   2521        if (NS_WARN_IF(!foundMatch)) {
   2522          NS_WARNING(
   2523              nsPrintfCString("Unexpected schema entry %s in Cache database",
   2524                              name.get())
   2525                  .get());
   2526          return Err(NS_ERROR_FAILURE);
   2527        }
   2528 
   2529        return Ok{};
   2530      }));
   2531 #endif
   2532 
   2533  return NS_OK;
   2534 }
   2535 
   2536 // -----
   2537 // Schema migration code
   2538 // -----
   2539 
   2540 using MigrationFunc = nsresult (*)(nsIFile&, mozIStorageConnection&, bool&);
   2541 struct Migration {
   2542  int32_t mFromVersion;
   2543  MigrationFunc mFunc;
   2544 };
   2545 
   2546 // Declare migration functions here.  Each function should upgrade
   2547 // the version by a single increment.  Don't skip versions.
   2548 nsresult MigrateFrom15To16(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2549                           bool& aRewriteSchema);
   2550 nsresult MigrateFrom16To17(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2551                           bool& aRewriteSchema);
   2552 nsresult MigrateFrom17To18(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2553                           bool& aRewriteSchema);
   2554 nsresult MigrateFrom18To19(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2555                           bool& aRewriteSchema);
   2556 nsresult MigrateFrom19To20(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2557                           bool& aRewriteSchema);
   2558 nsresult MigrateFrom20To21(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2559                           bool& aRewriteSchema);
   2560 nsresult MigrateFrom21To22(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2561                           bool& aRewriteSchema);
   2562 nsresult MigrateFrom22To23(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2563                           bool& aRewriteSchema);
   2564 nsresult MigrateFrom23To24(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2565                           bool& aRewriteSchema);
   2566 nsresult MigrateFrom24To25(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2567                           bool& aRewriteSchema);
   2568 nsresult MigrateFrom25To26(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2569                           bool& aRewriteSchema);
   2570 nsresult MigrateFrom26To27(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2571                           bool& aRewriteSchema);
   2572 nsresult MigrateFrom27To28(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2573                           bool& aRewriteSchema);
   2574 nsresult MigrateFrom28To29(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2575                           bool& aRewriteSchema);
   2576 // Configure migration functions to run for the given starting version.
   2577 constexpr Migration sMigrationList[] = {
   2578    Migration{15, MigrateFrom15To16}, Migration{16, MigrateFrom16To17},
   2579    Migration{17, MigrateFrom17To18}, Migration{18, MigrateFrom18To19},
   2580    Migration{19, MigrateFrom19To20}, Migration{20, MigrateFrom20To21},
   2581    Migration{21, MigrateFrom21To22}, Migration{22, MigrateFrom22To23},
   2582    Migration{23, MigrateFrom23To24}, Migration{24, MigrateFrom24To25},
   2583    Migration{25, MigrateFrom25To26}, Migration{26, MigrateFrom26To27},
   2584    Migration{27, MigrateFrom27To28}, Migration{28, MigrateFrom28To29},
   2585 };
   2586 
   2587 nsresult RewriteEntriesSchema(mozIStorageConnection& aConn) {
   2588  QM_TRY(
   2589      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("PRAGMA writable_schema = ON"_ns)));
   2590 
   2591  QM_TRY_INSPECT(
   2592      const auto& state,
   2593      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   2594          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   2595          "UPDATE sqlite_master SET sql=:sql WHERE name='entries'"_ns));
   2596 
   2597  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName(
   2598      "sql"_ns, nsDependentCString(kTableEntries))));
   2599  QM_TRY(MOZ_TO_RESULT(state->Execute()));
   2600 
   2601  QM_TRY(
   2602      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("PRAGMA writable_schema = OFF"_ns)));
   2603 
   2604  return NS_OK;
   2605 }
   2606 
   2607 nsresult Migrate(nsIFile& aDBDir, mozIStorageConnection& aConn) {
   2608  MOZ_ASSERT(!NS_IsMainThread());
   2609 
   2610  QM_TRY_UNWRAP(int32_t currentVersion, GetEffectiveSchemaVersion(aConn));
   2611 
   2612  bool rewriteSchema = false;
   2613 
   2614  while (currentVersion < kLatestSchemaVersion) {
   2615    // Wiping old databases is handled in DBAction because it requires
   2616    // making a whole new mozIStorageConnection.  Make sure we don't
   2617    // accidentally get here for one of those old databases.
   2618    MOZ_DIAGNOSTIC_ASSERT(currentVersion >= kFirstShippedSchemaVersion);
   2619 
   2620    for (const auto& migration : sMigrationList) {
   2621      if (migration.mFromVersion == currentVersion) {
   2622        bool shouldRewrite = false;
   2623        QM_TRY(MOZ_TO_RESULT(migration.mFunc(aDBDir, aConn, shouldRewrite)));
   2624        if (shouldRewrite) {
   2625          rewriteSchema = true;
   2626        }
   2627        break;
   2628      }
   2629    }
   2630 
   2631 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   2632    int32_t lastVersion = currentVersion;
   2633 #endif
   2634    QM_TRY_UNWRAP(currentVersion, GetEffectiveSchemaVersion(aConn));
   2635 
   2636    MOZ_DIAGNOSTIC_ASSERT(currentVersion > lastVersion);
   2637  }
   2638 
   2639  // Don't release assert this since people do sometimes share profiles
   2640  // across schema versions.  Our check in Validate() will catch it.
   2641  MOZ_ASSERT(currentVersion == kLatestSchemaVersion);
   2642 
   2643  nsresult rv = NS_OK;
   2644  if (rewriteSchema) {
   2645    // Now overwrite the master SQL for the entries table to remove the column
   2646    // default value.  This is also necessary for our Validate() method to
   2647    // pass on this database.
   2648    rv = RewriteEntriesSchema(aConn);
   2649  }
   2650 
   2651  return rv;
   2652 }
   2653 
   2654 nsresult MigrateFrom15To16(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2655                           bool& aRewriteSchema) {
   2656  MOZ_ASSERT(!NS_IsMainThread());
   2657 
   2658  // Add the request_redirect column with a default value of "follow".  Note,
   2659  // we only use a default value here because its required by ALTER TABLE and
   2660  // we need to apply the default "follow" to existing records in the table.
   2661  // We don't actually want to keep the default in the schema for future
   2662  // INSERTs.
   2663  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2664      "ALTER TABLE entries "
   2665      "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0"_ns)));
   2666 
   2667  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(16)));
   2668 
   2669  aRewriteSchema = true;
   2670 
   2671  return NS_OK;
   2672 }
   2673 
   2674 nsresult MigrateFrom16To17(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2675                           bool& aRewriteSchema) {
   2676  MOZ_ASSERT(!NS_IsMainThread());
   2677 
   2678  // This migration path removes the response_redirected and
   2679  // response_redirected_url columns from the entries table.  sqlite doesn't
   2680  // support removing a column from a table using ALTER TABLE, so we need to
   2681  // create a new table without those columns, fill it up with the existing
   2682  // data, and then drop the original table and rename the new one to the old
   2683  // one.
   2684 
   2685  // Create a new_entries table with the new fields as of version 17.
   2686  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2687      "CREATE TABLE new_entries ("
   2688      "id INTEGER NOT NULL PRIMARY KEY, "
   2689      "request_method TEXT NOT NULL, "
   2690      "request_url_no_query TEXT NOT NULL, "
   2691      "request_url_no_query_hash BLOB NOT NULL, "
   2692      "request_url_query TEXT NOT NULL, "
   2693      "request_url_query_hash BLOB NOT NULL, "
   2694      "request_referrer TEXT NOT NULL, "
   2695      "request_headers_guard INTEGER NOT NULL, "
   2696      "request_mode INTEGER NOT NULL, "
   2697      "request_credentials INTEGER NOT NULL, "
   2698      "request_contentpolicytype INTEGER NOT NULL, "
   2699      "request_cache INTEGER NOT NULL, "
   2700      "request_body_id TEXT NULL, "
   2701      "response_type INTEGER NOT NULL, "
   2702      "response_url TEXT NOT NULL, "
   2703      "response_status INTEGER NOT NULL, "
   2704      "response_status_text TEXT NOT NULL, "
   2705      "response_headers_guard INTEGER NOT NULL, "
   2706      "response_body_id TEXT NULL, "
   2707      "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
   2708      "response_principal_info TEXT NOT NULL, "
   2709      "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
   2710      "request_redirect INTEGER NOT NULL"
   2711      ")"_ns)));
   2712 
   2713  // Copy all of the data to the newly created table.
   2714  QM_TRY(
   2715      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO new_entries ("
   2716                                           "id, "
   2717                                           "request_method, "
   2718                                           "request_url_no_query, "
   2719                                           "request_url_no_query_hash, "
   2720                                           "request_url_query, "
   2721                                           "request_url_query_hash, "
   2722                                           "request_referrer, "
   2723                                           "request_headers_guard, "
   2724                                           "request_mode, "
   2725                                           "request_credentials, "
   2726                                           "request_contentpolicytype, "
   2727                                           "request_cache, "
   2728                                           "request_redirect, "
   2729                                           "request_body_id, "
   2730                                           "response_type, "
   2731                                           "response_url, "
   2732                                           "response_status, "
   2733                                           "response_status_text, "
   2734                                           "response_headers_guard, "
   2735                                           "response_body_id, "
   2736                                           "response_security_info_id, "
   2737                                           "response_principal_info, "
   2738                                           "cache_id "
   2739                                           ") SELECT "
   2740                                           "id, "
   2741                                           "request_method, "
   2742                                           "request_url_no_query, "
   2743                                           "request_url_no_query_hash, "
   2744                                           "request_url_query, "
   2745                                           "request_url_query_hash, "
   2746                                           "request_referrer, "
   2747                                           "request_headers_guard, "
   2748                                           "request_mode, "
   2749                                           "request_credentials, "
   2750                                           "request_contentpolicytype, "
   2751                                           "request_cache, "
   2752                                           "request_redirect, "
   2753                                           "request_body_id, "
   2754                                           "response_type, "
   2755                                           "response_url, "
   2756                                           "response_status, "
   2757                                           "response_status_text, "
   2758                                           "response_headers_guard, "
   2759                                           "response_body_id, "
   2760                                           "response_security_info_id, "
   2761                                           "response_principal_info, "
   2762                                           "cache_id "
   2763                                           "FROM entries;"_ns)));
   2764 
   2765  // Remove the old table.
   2766  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("DROP TABLE entries;"_ns)));
   2767 
   2768  // Rename new_entries to entries.
   2769  QM_TRY(MOZ_TO_RESULT(
   2770      aConn.ExecuteSimpleSQL("ALTER TABLE new_entries RENAME to entries;"_ns)));
   2771 
   2772  // Now, recreate our indices.
   2773  QM_TRY(MOZ_TO_RESULT(
   2774      aConn.ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest))));
   2775 
   2776  // Revalidate the foreign key constraints, and ensure that there are no
   2777  // violations.
   2778  QM_TRY_INSPECT(const bool& hasResult,
   2779                 quota::CreateAndExecuteSingleStepStatement<
   2780                     quota::SingleStepResult::ReturnNullIfNoResult>(
   2781                     aConn, "PRAGMA foreign_key_check;"_ns));
   2782 
   2783  QM_TRY(OkIf(!hasResult), NS_ERROR_FAILURE);
   2784 
   2785  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(17)));
   2786 
   2787  return NS_OK;
   2788 }
   2789 
   2790 nsresult MigrateFrom17To18(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2791                           bool& aRewriteSchema) {
   2792  MOZ_ASSERT(!NS_IsMainThread());
   2793 
   2794  // This migration is needed in order to remove "only-if-cached" RequestCache
   2795  // values from the database.  This enum value was removed from the spec in
   2796  // https://github.com/whatwg/fetch/issues/39 but we unfortunately happily
   2797  // accepted this value in the Request constructor.
   2798  //
   2799  // There is no good value to upgrade this to, so we just stick to "default".
   2800 
   2801  static_assert(int(RequestCache::Default) == 0,
   2802                "This is where the 0 below comes from!");
   2803  QM_TRY(MOZ_TO_RESULT(
   2804      aConn.ExecuteSimpleSQL("UPDATE entries SET request_cache = 0 "
   2805                             "WHERE request_cache = 5;"_ns)));
   2806 
   2807  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(18)));
   2808 
   2809  return NS_OK;
   2810 }
   2811 
   2812 nsresult MigrateFrom18To19(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2813                           bool& aRewriteSchema) {
   2814  MOZ_ASSERT(!NS_IsMainThread());
   2815 
   2816  // This migration is needed in order to update the RequestMode values for
   2817  // Request objects corresponding to a navigation content policy type to
   2818  // "navigate".
   2819 
   2820  static_assert(int(nsIContentPolicy::TYPE_DOCUMENT) == 6 &&
   2821                    int(nsIContentPolicy::TYPE_SUBDOCUMENT) == 7 &&
   2822                    int(nsIContentPolicy::TYPE_INTERNAL_FRAME) == 28 &&
   2823                    int(nsIContentPolicy::TYPE_INTERNAL_IFRAME) == 29 &&
   2824                    int(RequestMode::Navigate) == 3,
   2825                "This is where the numbers below come from!");
   2826  // 8 is former TYPE_REFRESH.
   2827 
   2828  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2829      "UPDATE entries SET request_mode = 3 "
   2830      "WHERE request_contentpolicytype IN (6, 7, 28, 29, 8);"_ns)));
   2831 
   2832  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(19)));
   2833 
   2834  return NS_OK;
   2835 }
   2836 
   2837 nsresult MigrateFrom19To20(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2838                           bool& aRewriteSchema) {
   2839  MOZ_ASSERT(!NS_IsMainThread());
   2840 
   2841  // Add the request_referrer_policy column with a default value of
   2842  // "no-referrer-when-downgrade".  Note, we only use a default value here
   2843  // because its required by ALTER TABLE and we need to apply the default
   2844  // "no-referrer-when-downgrade" to existing records in the table. We don't
   2845  // actually want to keep the default in the schema for future INSERTs.
   2846  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2847      "ALTER TABLE entries "
   2848      "ADD COLUMN request_referrer_policy INTEGER NOT NULL DEFAULT 2"_ns)));
   2849 
   2850  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(20)));
   2851 
   2852  aRewriteSchema = true;
   2853 
   2854  return NS_OK;
   2855 }
   2856 
   2857 nsresult MigrateFrom20To21(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2858                           bool& aRewriteSchema) {
   2859  MOZ_ASSERT(!NS_IsMainThread());
   2860 
   2861  // This migration creates response_url_list table to store response_url and
   2862  // removes the response_url column from the entries table.
   2863  // sqlite doesn't support removing a column from a table using ALTER TABLE,
   2864  // so we need to create a new table without those columns, fill it up with the
   2865  // existing data, and then drop the original table and rename the new one to
   2866  // the old one.
   2867 
   2868  // Create a new_entries table with the new fields as of version 21.
   2869  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2870      "CREATE TABLE new_entries ("
   2871      "id INTEGER NOT NULL PRIMARY KEY, "
   2872      "request_method TEXT NOT NULL, "
   2873      "request_url_no_query TEXT NOT NULL, "
   2874      "request_url_no_query_hash BLOB NOT NULL, "
   2875      "request_url_query TEXT NOT NULL, "
   2876      "request_url_query_hash BLOB NOT NULL, "
   2877      "request_referrer TEXT NOT NULL, "
   2878      "request_headers_guard INTEGER NOT NULL, "
   2879      "request_mode INTEGER NOT NULL, "
   2880      "request_credentials INTEGER NOT NULL, "
   2881      "request_contentpolicytype INTEGER NOT NULL, "
   2882      "request_cache INTEGER NOT NULL, "
   2883      "request_body_id TEXT NULL, "
   2884      "response_type INTEGER NOT NULL, "
   2885      "response_status INTEGER NOT NULL, "
   2886      "response_status_text TEXT NOT NULL, "
   2887      "response_headers_guard INTEGER NOT NULL, "
   2888      "response_body_id TEXT NULL, "
   2889      "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
   2890      "response_principal_info TEXT NOT NULL, "
   2891      "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
   2892      "request_redirect INTEGER NOT NULL, "
   2893      "request_referrer_policy INTEGER NOT NULL"
   2894      ")"_ns)));
   2895 
   2896  // Create a response_url_list table with the new fields as of version 21.
   2897  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2898      "CREATE TABLE response_url_list ("
   2899      "url TEXT NOT NULL, "
   2900      "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
   2901      ")"_ns)));
   2902 
   2903  // Copy all of the data to the newly created entries table.
   2904  QM_TRY(
   2905      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO new_entries ("
   2906                                           "id, "
   2907                                           "request_method, "
   2908                                           "request_url_no_query, "
   2909                                           "request_url_no_query_hash, "
   2910                                           "request_url_query, "
   2911                                           "request_url_query_hash, "
   2912                                           "request_referrer, "
   2913                                           "request_headers_guard, "
   2914                                           "request_mode, "
   2915                                           "request_credentials, "
   2916                                           "request_contentpolicytype, "
   2917                                           "request_cache, "
   2918                                           "request_redirect, "
   2919                                           "request_referrer_policy, "
   2920                                           "request_body_id, "
   2921                                           "response_type, "
   2922                                           "response_status, "
   2923                                           "response_status_text, "
   2924                                           "response_headers_guard, "
   2925                                           "response_body_id, "
   2926                                           "response_security_info_id, "
   2927                                           "response_principal_info, "
   2928                                           "cache_id "
   2929                                           ") SELECT "
   2930                                           "id, "
   2931                                           "request_method, "
   2932                                           "request_url_no_query, "
   2933                                           "request_url_no_query_hash, "
   2934                                           "request_url_query, "
   2935                                           "request_url_query_hash, "
   2936                                           "request_referrer, "
   2937                                           "request_headers_guard, "
   2938                                           "request_mode, "
   2939                                           "request_credentials, "
   2940                                           "request_contentpolicytype, "
   2941                                           "request_cache, "
   2942                                           "request_redirect, "
   2943                                           "request_referrer_policy, "
   2944                                           "request_body_id, "
   2945                                           "response_type, "
   2946                                           "response_status, "
   2947                                           "response_status_text, "
   2948                                           "response_headers_guard, "
   2949                                           "response_body_id, "
   2950                                           "response_security_info_id, "
   2951                                           "response_principal_info, "
   2952                                           "cache_id "
   2953                                           "FROM entries;"_ns)));
   2954 
   2955  // Copy reponse_url to the newly created response_url_list table.
   2956  QM_TRY(
   2957      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO response_url_list ("
   2958                                           "url, "
   2959                                           "entry_id "
   2960                                           ") SELECT "
   2961                                           "response_url, "
   2962                                           "id "
   2963                                           "FROM entries;"_ns)));
   2964 
   2965  // Remove the old table.
   2966  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("DROP TABLE entries;"_ns)));
   2967 
   2968  // Rename new_entries to entries.
   2969  QM_TRY(MOZ_TO_RESULT(
   2970      aConn.ExecuteSimpleSQL("ALTER TABLE new_entries RENAME to entries;"_ns)));
   2971 
   2972  // Now, recreate our indices.
   2973  QM_TRY(MOZ_TO_RESULT(
   2974      aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexEntriesRequest))));
   2975 
   2976  // Revalidate the foreign key constraints, and ensure that there are no
   2977  // violations.
   2978  QM_TRY_INSPECT(const bool& hasResult,
   2979                 quota::CreateAndExecuteSingleStepStatement<
   2980                     quota::SingleStepResult::ReturnNullIfNoResult>(
   2981                     aConn, "PRAGMA foreign_key_check;"_ns));
   2982 
   2983  QM_TRY(OkIf(!hasResult), NS_ERROR_FAILURE);
   2984 
   2985  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(21)));
   2986 
   2987  aRewriteSchema = true;
   2988 
   2989  return NS_OK;
   2990 }
   2991 
   2992 nsresult MigrateFrom21To22(nsIFile& aDBDir, mozIStorageConnection& aConn,
   2993                           bool& aRewriteSchema) {
   2994  MOZ_ASSERT(!NS_IsMainThread());
   2995 
   2996  // Add the request_integrity column.
   2997  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   2998      "ALTER TABLE entries "
   2999      "ADD COLUMN request_integrity TEXT NOT NULL DEFAULT '';"_ns)));
   3000 
   3001  QM_TRY(MOZ_TO_RESULT(
   3002      aConn.ExecuteSimpleSQL("UPDATE entries SET request_integrity = '';"_ns)));
   3003 
   3004  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(22)));
   3005 
   3006  aRewriteSchema = true;
   3007 
   3008  return NS_OK;
   3009 }
   3010 
   3011 nsresult MigrateFrom22To23(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3012                           bool& aRewriteSchema) {
   3013  MOZ_ASSERT(!NS_IsMainThread());
   3014 
   3015  // The only change between 22 and 23 was a different snappy compression
   3016  // format, but it's backwards-compatible.
   3017  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(23)));
   3018 
   3019  return NS_OK;
   3020 }
   3021 
   3022 nsresult MigrateFrom23To24(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3023                           bool& aRewriteSchema) {
   3024  MOZ_ASSERT(!NS_IsMainThread());
   3025 
   3026  // Add the request_url_fragment column.
   3027  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   3028      "ALTER TABLE entries "
   3029      "ADD COLUMN request_url_fragment TEXT NOT NULL DEFAULT ''"_ns)));
   3030 
   3031  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(24)));
   3032 
   3033  aRewriteSchema = true;
   3034 
   3035  return NS_OK;
   3036 }
   3037 
   3038 nsresult MigrateFrom24To25(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3039                           bool& aRewriteSchema) {
   3040  MOZ_ASSERT(!NS_IsMainThread());
   3041 
   3042  // The only change between 24 and 25 was a new nsIContentPolicy type.
   3043  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(25)));
   3044 
   3045  return NS_OK;
   3046 }
   3047 
   3048 nsresult MigrateFrom25To26(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3049                           bool& aRewriteSchema) {
   3050  MOZ_ASSERT(!NS_IsMainThread());
   3051 
   3052  // Add the response_padding_size column.
   3053  // Note: only opaque repsonse should be non-null interger.
   3054  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   3055      "ALTER TABLE entries "
   3056      "ADD COLUMN response_padding_size INTEGER NULL "_ns)));
   3057 
   3058  QM_TRY(MOZ_TO_RESULT(
   3059      aConn.ExecuteSimpleSQL("UPDATE entries SET response_padding_size = 0 "
   3060                             "WHERE response_type = 4"_ns  // opaque response
   3061                             )));
   3062 
   3063  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(26)));
   3064 
   3065  aRewriteSchema = true;
   3066 
   3067  return NS_OK;
   3068 }
   3069 
   3070 nsresult MigrateFrom26To27(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3071                           bool& aRewriteSchema) {
   3072  MOZ_ASSERT(!NS_IsMainThread());
   3073 
   3074  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(kHackyDowngradeSchemaVersion)));
   3075 
   3076  return NS_OK;
   3077 }
   3078 
   3079 nsresult MigrateFrom27To28(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3080                           bool& aRewriteSchema) {
   3081  MOZ_ASSERT(!NS_IsMainThread());
   3082 
   3083  // In Bug 1264178, we added a column request_integrity into table entries.
   3084  // However, at that time, the default value for the existing rows is NULL
   3085  // which against the statement in kTableEntries. Thus, we need to have another
   3086  // upgrade to update these values to an empty string.
   3087  QM_TRY(MOZ_TO_RESULT(
   3088      aConn.ExecuteSimpleSQL("UPDATE entries SET request_integrity = '' "
   3089                             "WHERE request_integrity is NULL;"_ns)));
   3090 
   3091  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(28)));
   3092 
   3093  return NS_OK;
   3094 }
   3095 
   3096 class BodyDiskSizeGetterFunction final : public mozIStorageFunction {
   3097 public:
   3098  explicit BodyDiskSizeGetterFunction(nsCOMPtr<nsIFile> aDBDir)
   3099      : mDBDir(std::move(aDBDir)), mTotalDiskUsage(0) {}
   3100 
   3101  NS_DECL_ISUPPORTS
   3102 
   3103  int64_t TotalDiskUsage() const { return mTotalDiskUsage; }
   3104 
   3105 private:
   3106  ~BodyDiskSizeGetterFunction() = default;
   3107 
   3108  NS_IMETHOD
   3109  OnFunctionCall(mozIStorageValueArray* aArguments,
   3110                 nsIVariant** aResult) override {
   3111    MOZ_ASSERT(aArguments);
   3112    MOZ_ASSERT(aResult);
   3113 
   3114    AUTO_PROFILER_LABEL("BodyDiskSizeGetterFunction::OnFunctionCall", DOM);
   3115 
   3116    uint32_t argc;
   3117    QM_TRY(MOZ_TO_RESULT(aArguments->GetNumEntries(&argc)));
   3118 
   3119    if (argc != 1) {
   3120      NS_WARNING("Don't call me with the wrong number of arguments!");
   3121      return NS_ERROR_UNEXPECTED;
   3122    }
   3123 
   3124    int32_t type;
   3125    QM_TRY(MOZ_TO_RESULT(aArguments->GetTypeOfIndex(0, &type)));
   3126 
   3127    if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
   3128      nsCOMPtr<nsIVariant> result = new mozilla::storage::NullVariant();
   3129 
   3130      result.forget(aResult);
   3131      return NS_OK;
   3132    }
   3133 
   3134    if (type != mozIStorageStatement::VALUE_TYPE_TEXT) {
   3135      NS_WARNING("Don't call me with the wrong type of arguments!");
   3136      return NS_ERROR_UNEXPECTED;
   3137    }
   3138 
   3139    QM_TRY_INSPECT(const auto& idString,
   3140                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, aArguments,
   3141                                                     GetUTF8String, 0));
   3142 
   3143    nsID id{};
   3144    QM_TRY(OkIf(id.Parse(idString.get())), Err(NS_ERROR_UNEXPECTED));
   3145 
   3146    QM_TRY_INSPECT(
   3147        const auto& fileSize,
   3148        QM_OR_ELSE_WARN_IF(
   3149            // Expression.
   3150            GetBodyDiskSize(*mDBDir, id),
   3151            // Predicate.
   3152            ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
   3153            // Fallback. If the file does no longer exist, treat
   3154            // it as 0-sized.
   3155            (ErrToOk<0, int64_t>)));
   3156 
   3157    CheckedInt64 totalDiskUsage = mTotalDiskUsage + fileSize;
   3158    mTotalDiskUsage =
   3159        totalDiskUsage.isValid() ? totalDiskUsage.value() : INT64_MAX;
   3160 
   3161    nsCOMPtr<nsIVariant> result =
   3162        new mozilla::storage::IntegerVariant(fileSize);
   3163 
   3164    result.forget(aResult);
   3165    return NS_OK;
   3166  }
   3167 
   3168  nsCOMPtr<nsIFile> mDBDir;
   3169  int64_t mTotalDiskUsage;
   3170 };
   3171 
   3172 NS_IMPL_ISUPPORTS(BodyDiskSizeGetterFunction, mozIStorageFunction)
   3173 
   3174 nsresult MigrateFrom28To29(nsIFile& aDBDir, mozIStorageConnection& aConn,
   3175                           bool& aRewriteSchema) {
   3176  MOZ_ASSERT(!NS_IsMainThread());
   3177 
   3178  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   3179      "ALTER TABLE entries "
   3180      "ADD COLUMN request_body_disk_size INTEGER NULL;"_ns)));
   3181 
   3182  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   3183      "ALTER TABLE entries "
   3184      "ADD COLUMN response_body_disk_size INTEGER NULL;"_ns)));
   3185 
   3186  RefPtr<BodyDiskSizeGetterFunction> bodyDiskSizeGetter =
   3187      new BodyDiskSizeGetterFunction(&aDBDir);
   3188 
   3189  constexpr auto bodyDiskSizeGetterName = "get_body_disk_size"_ns;
   3190 
   3191  QM_TRY(MOZ_TO_RESULT(
   3192      aConn.CreateFunction(bodyDiskSizeGetterName, 1, bodyDiskSizeGetter)));
   3193 
   3194  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
   3195      "UPDATE entries SET "
   3196      "request_body_disk_size = get_body_disk_size(request_body_id), "
   3197      "response_body_disk_size = get_body_disk_size(response_body_id);"_ns)));
   3198 
   3199  QM_TRY(MOZ_TO_RESULT(aConn.RemoveFunction(bodyDiskSizeGetterName)));
   3200 
   3201  QM_TRY(
   3202      MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableUsageInfo))));
   3203 
   3204  QM_TRY_INSPECT(
   3205      const auto& state,
   3206      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
   3207          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
   3208          "INSERT INTO usage_info VALUES(1, :total_disk_usage);"_ns));
   3209 
   3210  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName(
   3211      "total_disk_usage"_ns, bodyDiskSizeGetter->TotalDiskUsage())));
   3212 
   3213  QM_TRY(MOZ_TO_RESULT(state->Execute()));
   3214 
   3215  QM_TRY(MOZ_TO_RESULT(
   3216      aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesInsert))));
   3217 
   3218  QM_TRY(MOZ_TO_RESULT(
   3219      aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesUpdate))));
   3220 
   3221  QM_TRY(MOZ_TO_RESULT(
   3222      aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesDelete))));
   3223 
   3224  QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(29)));
   3225 
   3226  aRewriteSchema = true;
   3227 
   3228  return NS_OK;
   3229 }
   3230 
   3231 }  // anonymous namespace
   3232 }  // namespace mozilla::dom::cache::db