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