Coverage Report

Created: 2018-09-25 14:53

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