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/. */
7 #include "LocalStorageManager.h"
8 #include "StorageUtils.h"
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 #include "mozIStorageConnection.h"
17 #include "mozStorageHelper.h"
19 // Current version of the database schema
20 #define CURRENT_SCHEMA_VERSION 2
22 namespace mozilla::dom
{
24 using namespace StorageUtils
;
28 class nsReverseStringSQLFunction final
: public mozIStorageFunction
{
29 ~nsReverseStringSQLFunction() = default;
32 NS_DECL_MOZISTORAGEFUNCTION
35 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction
, mozIStorageFunction
)
38 nsReverseStringSQLFunction::OnFunctionCall(
39 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
42 nsAutoCString stringToReverse
;
43 rv
= aFunctionArguments
->GetUTF8String(0, stringToReverse
);
44 NS_ENSURE_SUCCESS(rv
, rv
);
47 ReverseString(stringToReverse
, result
);
49 RefPtr
<nsVariant
> outVar(new nsVariant());
50 rv
= outVar
->SetAsAUTF8String(result
);
51 NS_ENSURE_SUCCESS(rv
, rv
);
53 outVar
.forget(aResult
);
57 // "scope" to "origin attributes suffix" and "origin key" convertor
59 class ExtractOriginData
: protected mozilla::Tokenizer
{
61 ExtractOriginData(const nsACString
& scope
, nsACString
& suffix
,
63 : mozilla::Tokenizer(scope
) {
64 using mozilla::OriginAttributes
;
66 // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
67 // we don't find it, the scope is our new origin key and suffix
72 // Bail out if it isn't appId.
73 // AppId doesn't exist any more but we could have old storage data...
75 if (!ReadInteger(&appId
)) {
79 // Should be followed by a colon.
80 if (!CheckChar(':')) {
84 // Bail out if it isn't 'isolatedBrowserFlag'.
85 nsDependentCSubstring isolatedBrowserFlag
;
86 if (!ReadWord(isolatedBrowserFlag
)) {
90 bool inIsolatedMozBrowser
= isolatedBrowserFlag
== "t";
91 bool notInIsolatedBrowser
= isolatedBrowserFlag
== "f";
92 if (!inIsolatedMozBrowser
&& !notInIsolatedBrowser
) {
96 // Should be followed by a colon.
97 if (!CheckChar(':')) {
101 // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
102 // from it and take the rest as the origin key.
104 // If the profile went through schema 1 -> schema 0 -> schema 1 switching
105 // we may have stored the full attributes origin suffix when there were
106 // more than just appId and inIsolatedMozBrowser set on storage principal's
109 // To preserve full uniqueness we store this suffix to the scope key.
110 // Schema 0 code will just ignore it while keeping the scoping unique.
112 // The whole scope string is in one of the following forms (when we are
115 // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
116 // "1001:f:gro.allizom.rxd.:https:443"
118 // +- the parser cursor position.
120 // If there is '^', the full origin attributes suffix follows. We search
121 // for ':' since it is the delimiter used in the scope string and is never
122 // contained in the origin attributes suffix. Remaining string after
123 // the comma is the reversed-domain+schema+port tuple.
125 if (CheckChar('^')) {
128 if (t
.Equals(Token::Char(':'))) {
134 OriginAttributes
attrs(inIsolatedMozBrowser
);
135 attrs
.CreateSuffix(suffix
);
138 // Consume the rest of the input as "origin".
139 origin
.Assign(Substring(mCursor
, mEnd
));
143 class GetOriginParticular final
: public mozIStorageFunction
{
145 enum EParticular
{ ORIGIN_ATTRIBUTES_SUFFIX
, ORIGIN_KEY
};
147 explicit GetOriginParticular(EParticular aParticular
)
148 : mParticular(aParticular
) {}
151 GetOriginParticular() = delete;
152 ~GetOriginParticular() = default;
154 EParticular mParticular
;
157 NS_DECL_MOZISTORAGEFUNCTION
160 NS_IMPL_ISUPPORTS(GetOriginParticular
, mozIStorageFunction
)
163 GetOriginParticular::OnFunctionCall(mozIStorageValueArray
* aFunctionArguments
,
164 nsIVariant
** aResult
) {
168 rv
= aFunctionArguments
->GetUTF8String(0, scope
);
169 NS_ENSURE_SUCCESS(rv
, rv
);
171 nsAutoCString suffix
, origin
;
172 ExtractOriginData
extractor(scope
, suffix
, origin
);
174 nsCOMPtr
<nsIWritableVariant
> outVar(new nsVariant());
176 switch (mParticular
) {
177 case EParticular::ORIGIN_ATTRIBUTES_SUFFIX
:
178 rv
= outVar
->SetAsAUTF8String(suffix
);
180 case EParticular::ORIGIN_KEY
:
181 rv
= outVar
->SetAsAUTF8String(origin
);
185 NS_ENSURE_SUCCESS(rv
, rv
);
187 outVar
.forget(aResult
);
191 class StripOriginAddonId final
: public mozIStorageFunction
{
193 explicit StripOriginAddonId() = default;
196 ~StripOriginAddonId() = default;
199 NS_DECL_MOZISTORAGEFUNCTION
202 NS_IMPL_ISUPPORTS(StripOriginAddonId
, mozIStorageFunction
)
205 StripOriginAddonId::OnFunctionCall(mozIStorageValueArray
* aFunctionArguments
,
206 nsIVariant
** aResult
) {
209 nsAutoCString suffix
;
210 rv
= aFunctionArguments
->GetUTF8String(0, suffix
);
211 NS_ENSURE_SUCCESS(rv
, rv
);
213 // Deserialize and re-serialize to automatically drop any obsolete origin
216 bool ok
= oa
.PopulateFromSuffix(suffix
);
217 NS_ENSURE_TRUE(ok
, NS_ERROR_FAILURE
);
219 nsAutoCString newSuffix
;
220 oa
.CreateSuffix(newSuffix
);
222 nsCOMPtr
<nsIWritableVariant
> outVar
= new nsVariant();
223 rv
= outVar
->SetAsAUTF8String(newSuffix
);
224 NS_ENSURE_SUCCESS(rv
, rv
);
226 outVar
.forget(aResult
);
230 nsresult
CreateSchema1Tables(mozIStorageConnection
* aWorkerConnection
) {
233 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
234 "CREATE TABLE IF NOT EXISTS webappsstore2 ("
235 "originAttributes TEXT, "
237 "scope TEXT, " // Only for schema0 downgrade compatibility
240 NS_ENSURE_SUCCESS(rv
, rv
);
242 rv
= aWorkerConnection
->ExecuteSimpleSQL(
243 nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
244 " ON webappsstore2(originAttributes, originKey, key)"));
245 NS_ENSURE_SUCCESS(rv
, rv
);
250 nsresult
TablesExist(mozIStorageConnection
* aWorkerConnection
,
251 bool* aWebappsstore2Exists
, bool* aWebappsstoreExists
,
252 bool* aMoz_webappsstoreExists
) {
254 aWorkerConnection
->TableExists("webappsstore2"_ns
, aWebappsstore2Exists
);
255 NS_ENSURE_SUCCESS(rv
, rv
);
256 rv
= aWorkerConnection
->TableExists("webappsstore"_ns
, aWebappsstoreExists
);
257 NS_ENSURE_SUCCESS(rv
, rv
);
258 rv
= aWorkerConnection
->TableExists("moz_webappsstore"_ns
,
259 aMoz_webappsstoreExists
);
260 NS_ENSURE_SUCCESS(rv
, rv
);
265 nsresult
CreateCurrentSchemaOnEmptyTableInternal(
266 mozIStorageConnection
* aWorkerConnection
) {
267 nsresult rv
= CreateSchema1Tables(aWorkerConnection
);
268 NS_ENSURE_SUCCESS(rv
, rv
);
270 rv
= aWorkerConnection
->SetSchemaVersion(CURRENT_SCHEMA_VERSION
);
271 NS_ENSURE_SUCCESS(rv
, rv
);
278 namespace StorageDBUpdater
{
280 nsresult
CreateCurrentSchema(mozIStorageConnection
* aConnection
) {
281 mozStorageTransaction
transaction(aConnection
, false);
283 nsresult rv
= transaction
.Start();
284 NS_ENSURE_SUCCESS(rv
, rv
);
286 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
289 nsresult rv
= aConnection
->GetSchemaVersion(&schemaVer
);
290 NS_ENSURE_SUCCESS(rv
, rv
);
292 MOZ_DIAGNOSTIC_ASSERT(0 == schemaVer
);
294 bool webappsstore2Exists
, webappsstoreExists
, moz_webappsstoreExists
;
295 rv
= TablesExist(aConnection
, &webappsstore2Exists
, &webappsstoreExists
,
296 &moz_webappsstoreExists
);
297 NS_ENSURE_SUCCESS(rv
, rv
);
299 MOZ_DIAGNOSTIC_ASSERT(!webappsstore2Exists
&& !webappsstoreExists
&&
300 !moz_webappsstoreExists
);
304 rv
= CreateCurrentSchemaOnEmptyTableInternal(aConnection
);
305 NS_ENSURE_SUCCESS(rv
, rv
);
307 rv
= transaction
.Commit();
308 NS_ENSURE_SUCCESS(rv
, rv
);
313 nsresult
Update(mozIStorageConnection
* aWorkerConnection
) {
314 mozStorageTransaction
transaction(aWorkerConnection
, false);
316 nsresult rv
= transaction
.Start();
317 NS_ENSURE_SUCCESS(rv
, rv
);
319 bool doVacuum
= false;
322 rv
= aWorkerConnection
->GetSchemaVersion(&schemaVer
);
323 NS_ENSURE_SUCCESS(rv
, rv
);
325 // downgrade (v0) -> upgrade (v1+) specific code
326 if (schemaVer
>= 1) {
327 bool schema0IndexExists
;
328 rv
= aWorkerConnection
->IndexExists("scope_key_index"_ns
,
329 &schema0IndexExists
);
330 NS_ENSURE_SUCCESS(rv
, rv
);
332 if (schema0IndexExists
) {
333 // If this index exists, the database (already updated to schema >1)
334 // has been run again on schema 0 code. That recreated that index
335 // and might store some new rows while updating only the 'scope' column.
336 // For such added rows we must fill the new 'origin*' columns correctly
337 // otherwise there would be a data loss. The safest way to do it is to
338 // simply run the whole update to schema 1 again.
345 bool webappsstore2Exists
, webappsstoreExists
, moz_webappsstoreExists
;
346 rv
= TablesExist(aWorkerConnection
, &webappsstore2Exists
,
347 &webappsstoreExists
, &moz_webappsstoreExists
);
348 NS_ENSURE_SUCCESS(rv
, rv
);
350 if (!webappsstore2Exists
&& !webappsstoreExists
&&
351 !moz_webappsstoreExists
) {
352 // The database is empty, this is the first start. Just create the
353 // schema table and break to the next version to update to, i.e. bypass
354 // update from the old version.
356 // XXX What does "break to the next version to update to" mean here? It
357 // seems to refer to the 'break' statement below, but that breaks out of
358 // the 'switch' statement and continues with committing the transaction.
359 // Either this is wrong, or the comment above is misleading.
361 rv
= CreateCurrentSchemaOnEmptyTableInternal(aWorkerConnection
);
362 NS_ENSURE_SUCCESS(rv
, rv
);
369 // Ensure Gecko 1.9.1 storage table
370 rv
= aWorkerConnection
->ExecuteSimpleSQL(
371 nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
377 NS_ENSURE_SUCCESS(rv
, rv
);
379 rv
= aWorkerConnection
->ExecuteSimpleSQL(
380 nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
381 " ON webappsstore2(scope, key)"));
382 NS_ENSURE_SUCCESS(rv
, rv
);
384 nsCOMPtr
<mozIStorageFunction
> function1(new nsReverseStringSQLFunction());
385 NS_ENSURE_TRUE(function1
, NS_ERROR_OUT_OF_MEMORY
);
387 rv
= aWorkerConnection
->CreateFunction("REVERSESTRING"_ns
, 1, function1
);
388 NS_ENSURE_SUCCESS(rv
, rv
);
390 // Check if there is storage of Gecko 1.9.0 and if so, upgrade that
391 // storage to actual webappsstore2 table and drop the obsolete table.
392 // First process this newer table upgrade to priority potential duplicates
393 // from older storage table.
394 if (webappsstoreExists
) {
395 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
396 "INSERT OR IGNORE INTO "
397 "webappsstore2(scope, key, value, secure, owner) "
398 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
399 "FROM webappsstore"));
400 NS_ENSURE_SUCCESS(rv
, rv
);
402 rv
= aWorkerConnection
->ExecuteSimpleSQL("DROP TABLE webappsstore"_ns
);
403 NS_ENSURE_SUCCESS(rv
, rv
);
406 // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
407 // to actual webappsstore2 table and drop the obsolete table. Potential
408 // duplicates will be ignored.
409 if (moz_webappsstoreExists
) {
410 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
411 "INSERT OR IGNORE INTO "
412 "webappsstore2(scope, key, value, secure, owner) "
413 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
414 "FROM moz_webappsstore"));
415 NS_ENSURE_SUCCESS(rv
, rv
);
417 rv
= aWorkerConnection
->ExecuteSimpleSQL(
418 "DROP TABLE moz_webappsstore"_ns
);
419 NS_ENSURE_SUCCESS(rv
, rv
);
422 aWorkerConnection
->RemoveFunction("REVERSESTRING"_ns
);
424 // Update the scoping to match the new implememntation: split to oa suffix
425 // and origin key First rename the old table, we want to remove some
426 // columns no longer needed, but even before that drop all indexes from it
427 // (CREATE IF NOT EXISTS for index on the new table would falsely find the
429 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
430 "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
431 NS_ENSURE_SUCCESS(rv
, rv
);
433 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
434 "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
435 NS_ENSURE_SUCCESS(rv
, rv
);
437 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
438 "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
439 NS_ENSURE_SUCCESS(rv
, rv
);
441 nsCOMPtr
<mozIStorageFunction
> oaSuffixFunc(new GetOriginParticular(
442 GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX
));
443 rv
= aWorkerConnection
->CreateFunction("GET_ORIGIN_SUFFIX"_ns
, 1,
445 NS_ENSURE_SUCCESS(rv
, rv
);
447 nsCOMPtr
<mozIStorageFunction
> originKeyFunc(
448 new GetOriginParticular(GetOriginParticular::ORIGIN_KEY
));
449 rv
= aWorkerConnection
->CreateFunction("GET_ORIGIN_KEY"_ns
, 1,
451 NS_ENSURE_SUCCESS(rv
, rv
);
453 // Here we ensure this schema tables when we are updating.
454 rv
= CreateSchema1Tables(aWorkerConnection
);
455 NS_ENSURE_SUCCESS(rv
, rv
);
457 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
458 "INSERT OR IGNORE INTO "
459 "webappsstore2 (originAttributes, originKey, scope, key, value) "
460 "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, "
462 "FROM webappsstore2_old"));
463 NS_ENSURE_SUCCESS(rv
, rv
);
465 rv
= aWorkerConnection
->ExecuteSimpleSQL(
466 "DROP TABLE webappsstore2_old"_ns
);
467 NS_ENSURE_SUCCESS(rv
, rv
);
469 aWorkerConnection
->RemoveFunction("GET_ORIGIN_SUFFIX"_ns
);
470 aWorkerConnection
->RemoveFunction("GET_ORIGIN_KEY"_ns
);
472 rv
= aWorkerConnection
->SetSchemaVersion(1);
473 NS_ENSURE_SUCCESS(rv
, rv
);
478 nsCOMPtr
<mozIStorageFunction
> oaStripAddonId(new StripOriginAddonId());
479 rv
= aWorkerConnection
->CreateFunction("STRIP_ADDON_ID"_ns
, 1,
481 NS_ENSURE_SUCCESS(rv
, rv
);
483 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
484 "UPDATE webappsstore2 "
485 "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
486 "WHERE originAttributes LIKE '^%'"));
487 NS_ENSURE_SUCCESS(rv
, rv
);
489 aWorkerConnection
->RemoveFunction("STRIP_ADDON_ID"_ns
);
491 rv
= aWorkerConnection
->SetSchemaVersion(2);
492 NS_ENSURE_SUCCESS(rv
, rv
);
496 case CURRENT_SCHEMA_VERSION
:
497 // Ensure the tables and indexes are up. This is mostly a no-op
498 // in common scenarios.
499 rv
= CreateSchema1Tables(aWorkerConnection
);
500 NS_ENSURE_SUCCESS(rv
, rv
);
502 // Nothing more to do here, this is the current schema version
510 rv
= transaction
.Commit();
511 NS_ENSURE_SUCCESS(rv
, rv
);
514 // In some cases this can make the disk file of the database significantly
515 // smaller. VACUUM cannot be executed inside a transaction.
516 rv
= aWorkerConnection
->ExecuteSimpleSQL("VACUUM"_ns
);
517 NS_ENSURE_SUCCESS(rv
, rv
);
523 } // namespace StorageDBUpdater
524 } // namespace mozilla::dom