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