Source code

Revision control

Other Tools

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