Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/storage/StorageDBUpdater.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 "LocalStorageManager.h"
8
#include "StorageUtils.h"
9
10
#include "mozIStorageBindingParamsArray.h"
11
#include "mozIStorageBindingParams.h"
12
#include "mozIStorageValueArray.h"
13
#include "mozIStorageFunction.h"
14
#include "mozilla/BasePrincipal.h"
15
#include "nsVariant.h"
16
#include "mozilla/Services.h"
17
#include "mozilla/Tokenizer.h"
18
19
// Current version of the database schema
20
0
#define CURRENT_SCHEMA_VERSION 2
21
22
namespace mozilla {
23
namespace dom {
24
25
using namespace StorageUtils;
26
27
namespace {
28
29
class nsReverseStringSQLFunction final : public mozIStorageFunction
30
{
31
0
  ~nsReverseStringSQLFunction() {}
32
33
  NS_DECL_ISUPPORTS
34
  NS_DECL_MOZISTORAGEFUNCTION
35
};
36
37
NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
38
39
NS_IMETHODIMP
40
nsReverseStringSQLFunction::OnFunctionCall(
41
    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
42
0
{
43
0
  nsresult rv;
44
0
45
0
  nsAutoCString stringToReverse;
46
0
  rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
47
0
  NS_ENSURE_SUCCESS(rv, rv);
48
0
49
0
  nsAutoCString result;
50
0
  ReverseString(stringToReverse, result);
51
0
52
0
  RefPtr<nsVariant> outVar(new nsVariant());
53
0
  rv = outVar->SetAsAUTF8String(result);
54
0
  NS_ENSURE_SUCCESS(rv, rv);
55
0
56
0
  outVar.forget(aResult);
57
0
  return NS_OK;
58
0
}
59
60
// "scope" to "origin attributes suffix" and "origin key" convertor
61
62
class ExtractOriginData : protected mozilla::Tokenizer
63
{
64
public:
65
  ExtractOriginData(const nsACString& scope, nsACString& suffix,
66
                    nsACString& origin)
67
    : mozilla::Tokenizer(scope)
68
0
  {
69
0
    using mozilla::OriginAttributes;
70
0
71
0
    // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
72
0
    // we don't find it, the scope is our new origin key and suffix
73
0
    // is empty.
74
0
    suffix.Truncate();
75
0
    origin.Assign(scope);
76
0
77
0
    // Bail out if it isn't appId.
78
0
    uint32_t appId;
79
0
    if (!ReadInteger(&appId)) {
80
0
      return;
81
0
    }
82
0
83
0
    // Should be followed by a colon.
84
0
    if (!CheckChar(':')) {
85
0
      return;
86
0
    }
87
0
88
0
    // Bail out if it isn't 'isolatedBrowserFlag'.
89
0
    nsDependentCSubstring isolatedBrowserFlag;
90
0
    if (!ReadWord(isolatedBrowserFlag)) {
91
0
      return;
92
0
    }
93
0
94
0
    bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
95
0
    bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
96
0
    if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
97
0
      return;
98
0
    }
99
0
100
0
    // Should be followed by a colon.
101
0
    if (!CheckChar(':')) {
102
0
      return;
103
0
    }
104
0
105
0
    // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
106
0
    // from it and take the rest as the origin key.
107
0
108
0
    // If the profile went through schema 1 -> schema 0 -> schema 1 switching
109
0
    // we may have stored the full attributes origin suffix when there were
110
0
    // more than just appId and inIsolatedMozBrowser set on storage principal's
111
0
    // OriginAttributes.
112
0
    //
113
0
    // To preserve full uniqueness we store this suffix to the scope key.
114
0
    // Schema 0 code will just ignore it while keeping the scoping unique.
115
0
    //
116
0
    // The whole scope string is in one of the following forms (when we are
117
0
    // here):
118
0
    //
119
0
    // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
120
0
    // "1001:f:gro.allizom.rxd.:https:443"
121
0
    //         |
122
0
    //         +- the parser cursor position.
123
0
    //
124
0
    // If there is '^', the full origin attributes suffix follows.  We search
125
0
    // for ':' since it is the delimiter used in the scope string and is never
126
0
    // contained in the origin attributes suffix.  Remaining string after
127
0
    // the comma is the reversed-domain+schema+port tuple.
128
0
    Record();
129
0
    if (CheckChar('^')) {
130
0
      Token t;
131
0
      while (Next(t)) {
132
0
        if (t.Equals(Token::Char(':'))) {
133
0
          Claim(suffix);
134
0
          break;
135
0
        }
136
0
      }
137
0
    } else {
138
0
      OriginAttributes attrs(appId, inIsolatedMozBrowser);
139
0
      attrs.CreateSuffix(suffix);
140
0
    }
141
0
142
0
    // Consume the rest of the input as "origin".
143
0
    origin.Assign(Substring(mCursor, mEnd));
144
0
  }
145
};
146
147
class GetOriginParticular final : public mozIStorageFunction
148
{
149
public:
150
  enum EParticular {
151
    ORIGIN_ATTRIBUTES_SUFFIX,
152
    ORIGIN_KEY
153
  };
154
155
  explicit GetOriginParticular(EParticular aParticular)
156
0
    : mParticular(aParticular) {}
157
158
private:
159
  GetOriginParticular() = delete;
160
0
  ~GetOriginParticular() {}
161
162
  EParticular mParticular;
163
164
  NS_DECL_ISUPPORTS
165
  NS_DECL_MOZISTORAGEFUNCTION
166
};
167
168
NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
169
170
NS_IMETHODIMP
171
GetOriginParticular::OnFunctionCall(
172
    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
173
0
{
174
0
  nsresult rv;
175
0
176
0
  nsAutoCString scope;
177
0
  rv = aFunctionArguments->GetUTF8String(0, scope);
178
0
  NS_ENSURE_SUCCESS(rv, rv);
179
0
180
0
  nsAutoCString suffix, origin;
181
0
  ExtractOriginData extractor(scope, suffix, origin);
182
0
183
0
  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
184
0
185
0
  switch (mParticular) {
186
0
  case EParticular::ORIGIN_ATTRIBUTES_SUFFIX:
187
0
    rv = outVar->SetAsAUTF8String(suffix);
188
0
    break;
189
0
  case EParticular::ORIGIN_KEY:
190
0
    rv = outVar->SetAsAUTF8String(origin);
191
0
    break;
192
0
  }
193
0
194
0
  NS_ENSURE_SUCCESS(rv, rv);
195
0
196
0
  outVar.forget(aResult);
197
0
  return NS_OK;
198
0
}
199
200
class StripOriginAddonId final : public mozIStorageFunction
201
{
202
public:
203
0
  explicit StripOriginAddonId() {}
204
205
private:
206
0
  ~StripOriginAddonId() {}
207
208
  NS_DECL_ISUPPORTS
209
  NS_DECL_MOZISTORAGEFUNCTION
210
};
211
212
NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
213
214
NS_IMETHODIMP
215
StripOriginAddonId::OnFunctionCall(
216
    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
217
0
{
218
0
  nsresult rv;
219
0
220
0
  nsAutoCString suffix;
221
0
  rv = aFunctionArguments->GetUTF8String(0, suffix);
222
0
  NS_ENSURE_SUCCESS(rv, rv);
223
0
224
0
  // Deserialize and re-serialize to automatically drop any obsolete origin
225
0
  // attributes.
226
0
  OriginAttributes oa;
227
0
  bool ok = oa.PopulateFromSuffix(suffix);
228
0
  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
229
0
230
0
  nsAutoCString newSuffix;
231
0
  oa.CreateSuffix(newSuffix);
232
0
233
0
  nsCOMPtr<nsIWritableVariant> outVar = new nsVariant();
234
0
  rv = outVar->SetAsAUTF8String(newSuffix);
235
0
  NS_ENSURE_SUCCESS(rv, rv);
236
0
237
0
  outVar.forget(aResult);
238
0
  return NS_OK;
239
0
}
240
241
} // namespace
242
243
namespace StorageDBUpdater {
244
245
nsresult CreateSchema1Tables(mozIStorageConnection *aWorkerConnection)
246
0
{
247
0
  nsresult rv;
248
0
249
0
  rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
250
0
          "CREATE TABLE IF NOT EXISTS webappsstore2 ("
251
0
          "originAttributes TEXT, "
252
0
          "originKey TEXT, "
253
0
          "scope TEXT, " // Only for schema0 downgrade compatibility
254
0
          "key TEXT, "
255
0
          "value TEXT)"));
256
0
  NS_ENSURE_SUCCESS(rv, rv);
257
0
258
0
  rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
259
0
        "CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
260
0
        " ON webappsstore2(originAttributes, originKey, key)"));
261
0
  NS_ENSURE_SUCCESS(rv, rv);
262
0
263
0
  return NS_OK;
264
0
}
265
266
nsresult Update(mozIStorageConnection *aWorkerConnection)
267
0
{
268
0
  nsresult rv;
269
0
270
0
  mozStorageTransaction transaction(aWorkerConnection, false);
271
0
272
0
  bool doVacuum = false;
273
0
274
0
  int32_t schemaVer;
275
0
  rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
276
0
  NS_ENSURE_SUCCESS(rv, rv);
277
0
278
0
  // downgrade (v0) -> upgrade (v1+) specific code
279
0
  if (schemaVer >= 1) {
280
0
    bool schema0IndexExists;
281
0
    rv = aWorkerConnection->IndexExists(NS_LITERAL_CSTRING("scope_key_index"),
282
0
                                        &schema0IndexExists);
283
0
    NS_ENSURE_SUCCESS(rv, rv);
284
0
285
0
    if (schema0IndexExists) {
286
0
      // If this index exists, the database (already updated to schema >1)
287
0
      // has been run again on schema 0 code.  That recreated that index
288
0
      // and might store some new rows while updating only the 'scope' column.
289
0
      // For such added rows we must fill the new 'origin*' columns correctly
290
0
      // otherwise there would be a data loss.  The safest way to do it is to
291
0
      // simply run the whole update to schema 1 again.
292
0
      schemaVer = 0;
293
0
    }
294
0
  }
295
0
296
0
  switch (schemaVer) {
297
0
  case 0: {
298
0
    bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
299
0
300
0
    rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore2"),
301
0
                                        &webappsstore2Exists);
302
0
    NS_ENSURE_SUCCESS(rv, rv);
303
0
    rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
304
0
                                        &webappsstoreExists);
305
0
    NS_ENSURE_SUCCESS(rv, rv);
306
0
    rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
307
0
                                        &moz_webappsstoreExists);
308
0
    NS_ENSURE_SUCCESS(rv, rv);
309
0
310
0
    if (!webappsstore2Exists && !webappsstoreExists &&
311
0
        !moz_webappsstoreExists) {
312
0
      // The database is empty, this is the first start.  Just create the schema
313
0
      // table and break to the next version to update to, i.e. bypass update
314
0
      // from the old version.
315
0
316
0
      rv = CreateSchema1Tables(aWorkerConnection);
317
0
      NS_ENSURE_SUCCESS(rv, rv);
318
0
319
0
      rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
320
0
      NS_ENSURE_SUCCESS(rv, rv);
321
0
322
0
      break;
323
0
    }
324
0
325
0
    doVacuum = true;
326
0
327
0
    // Ensure Gecko 1.9.1 storage table
328
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
329
0
           "CREATE TABLE IF NOT EXISTS webappsstore2 ("
330
0
           "scope TEXT, "
331
0
           "key TEXT, "
332
0
           "value TEXT, "
333
0
           "secure INTEGER, "
334
0
           "owner TEXT)"));
335
0
    NS_ENSURE_SUCCESS(rv, rv);
336
0
337
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
338
0
          "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
339
0
          " ON webappsstore2(scope, key)"));
340
0
    NS_ENSURE_SUCCESS(rv, rv);
341
0
342
0
    nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
343
0
    NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
344
0
345
0
    rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"),
346
0
                                           1, function1);
347
0
    NS_ENSURE_SUCCESS(rv, rv);
348
0
349
0
    // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
350
0
    // to actual webappsstore2 table and drop the obsolete table. First process
351
0
    // this newer table upgrade to priority potential duplicates from older
352
0
    // storage table.
353
0
    if (webappsstoreExists) {
354
0
      rv = aWorkerConnection->ExecuteSimpleSQL(
355
0
        NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
356
0
                           "webappsstore2(scope, key, value, secure, owner) "
357
0
                           "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
358
0
                           "FROM webappsstore"));
359
0
      NS_ENSURE_SUCCESS(rv, rv);
360
0
361
0
      rv = aWorkerConnection->ExecuteSimpleSQL(
362
0
        NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
363
0
      NS_ENSURE_SUCCESS(rv, rv);
364
0
    }
365
0
366
0
    // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
367
0
    // to actual webappsstore2 table and drop the obsolete table. Potential
368
0
    // duplicates will be ignored.
369
0
    if (moz_webappsstoreExists) {
370
0
      rv = aWorkerConnection->ExecuteSimpleSQL(
371
0
        NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
372
0
                           "webappsstore2(scope, key, value, secure, owner) "
373
0
                           "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
374
0
                           "FROM moz_webappsstore"));
375
0
      NS_ENSURE_SUCCESS(rv, rv);
376
0
377
0
      rv = aWorkerConnection->ExecuteSimpleSQL(
378
0
        NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
379
0
      NS_ENSURE_SUCCESS(rv, rv);
380
0
    }
381
0
382
0
    aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("REVERSESTRING"));
383
0
384
0
    // Update the scoping to match the new implememntation: split to oa suffix
385
0
    // and origin key First rename the old table, we want to remove some columns
386
0
    // no longer needed, but even before that drop all indexes from it (CREATE
387
0
    // IF NOT EXISTS for index on the new table would falsely find the index!)
388
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
389
0
          "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
390
0
    NS_ENSURE_SUCCESS(rv, rv);
391
0
392
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
393
0
          "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
394
0
    NS_ENSURE_SUCCESS(rv, rv);
395
0
396
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
397
0
          "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
398
0
    NS_ENSURE_SUCCESS(rv, rv);
399
0
400
0
    nsCOMPtr<mozIStorageFunction> oaSuffixFunc(
401
0
      new GetOriginParticular(GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
402
0
    rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"),
403
0
                                           1, oaSuffixFunc);
404
0
    NS_ENSURE_SUCCESS(rv, rv);
405
0
406
0
    nsCOMPtr<mozIStorageFunction> originKeyFunc(
407
0
      new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
408
0
    rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"),
409
0
                                           1, originKeyFunc);
410
0
    NS_ENSURE_SUCCESS(rv, rv);
411
0
412
0
    // Here we ensure this schema tables when we are updating.
413
0
    rv = CreateSchema1Tables(aWorkerConnection);
414
0
    NS_ENSURE_SUCCESS(rv, rv);
415
0
416
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
417
0
          "INSERT OR IGNORE INTO "
418
0
          "webappsstore2 (originAttributes, originKey, scope, key, value) "
419
0
          "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, value "
420
0
          "FROM webappsstore2_old"));
421
0
    NS_ENSURE_SUCCESS(rv, rv);
422
0
423
0
    rv = aWorkerConnection->ExecuteSimpleSQL(
424
0
      NS_LITERAL_CSTRING("DROP TABLE webappsstore2_old"));
425
0
    NS_ENSURE_SUCCESS(rv, rv);
426
0
427
0
    aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"));
428
0
    aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"));
429
0
430
0
    rv = aWorkerConnection->SetSchemaVersion(1);
431
0
    NS_ENSURE_SUCCESS(rv, rv);
432
0
433
0
    MOZ_FALLTHROUGH;
434
0
  }
435
0
  case 1: {
436
0
    nsCOMPtr<mozIStorageFunction> oaStripAddonId(
437
0
      new StripOriginAddonId());
438
0
    rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("STRIP_ADDON_ID"), 1, oaStripAddonId);
439
0
    NS_ENSURE_SUCCESS(rv, rv);
440
0
441
0
    rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
442
0
          "UPDATE webappsstore2 "
443
0
          "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
444
0
          "WHERE originAttributes LIKE '^%'"));
445
0
    NS_ENSURE_SUCCESS(rv, rv);
446
0
447
0
    aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("STRIP_ADDON_ID"));
448
0
449
0
    rv = aWorkerConnection->SetSchemaVersion(2);
450
0
    NS_ENSURE_SUCCESS(rv, rv);
451
0
452
0
    MOZ_FALLTHROUGH;
453
0
  }
454
0
  case CURRENT_SCHEMA_VERSION:
455
0
    // Ensure the tables and indexes are up.  This is mostly a no-op
456
0
    // in common scenarios.
457
0
    rv = CreateSchema1Tables(aWorkerConnection);
458
0
    NS_ENSURE_SUCCESS(rv, rv);
459
0
460
0
    // Nothing more to do here, this is the current schema version
461
0
    break;
462
0
463
0
  default:
464
0
    MOZ_ASSERT(false);
465
0
    break;
466
0
  } // switch
467
0
468
0
  rv = transaction.Commit();
469
0
  NS_ENSURE_SUCCESS(rv, rv);
470
0
471
0
  if (doVacuum) {
472
0
    // In some cases this can make the disk file of the database significantly
473
0
    // smaller.  VACUUM cannot be executed inside a transaction.
474
0
    rv = aWorkerConnection->ExecuteSimpleSQL(
475
0
      NS_LITERAL_CSTRING("VACUUM"));
476
0
    NS_ENSURE_SUCCESS(rv, rv);
477
0
  }
478
0
479
0
  return NS_OK;
480
0
}
481
482
} // namespace StorageDBUpdater
483
} // namespace dom
484
} // namespace mozilla