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