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"
18 #include "mozilla/StorageOriginAttributes.h"
20 // Current version of the database schema
21 #define CURRENT_SCHEMA_VERSION 2
23 namespace mozilla::dom
{
25 using namespace StorageUtils
;
29 class nsReverseStringSQLFunction final
: public mozIStorageFunction
{
30 ~nsReverseStringSQLFunction() = default;
33 NS_DECL_MOZISTORAGEFUNCTION
36 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction
, mozIStorageFunction
)
39 nsReverseStringSQLFunction::OnFunctionCall(
40 mozIStorageValueArray
* aFunctionArguments
, nsIVariant
** aResult
) {
43 nsAutoCString stringToReverse
;
44 rv
= aFunctionArguments
->GetUTF8String(0, stringToReverse
);
45 NS_ENSURE_SUCCESS(rv
, rv
);
48 ReverseString(stringToReverse
, result
);
50 RefPtr
<nsVariant
> outVar(new nsVariant());
51 rv
= outVar
->SetAsAUTF8String(result
);
52 NS_ENSURE_SUCCESS(rv
, rv
);
54 outVar
.forget(aResult
);
58 // "scope" to "origin attributes suffix" and "origin key" convertor
60 class ExtractOriginData
: protected mozilla::Tokenizer
{
62 ExtractOriginData(const nsACString
& scope
, nsACString
& suffix
,
64 : mozilla::Tokenizer(scope
) {
65 using mozilla::OriginAttributes
;
67 // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
68 // we don't find it, the scope is our new origin key and suffix
73 // Bail out if it isn't appId.
74 // AppId doesn't exist any more but we could have old storage data...
76 if (!ReadInteger(&appId
)) {
80 // Should be followed by a colon.
81 if (!CheckChar(':')) {
85 // Bail out if it isn't 'isolatedBrowserFlag'.
86 nsDependentCSubstring isolatedBrowserFlag
;
87 if (!ReadWord(isolatedBrowserFlag
)) {
91 bool inIsolatedMozBrowser
= isolatedBrowserFlag
== "t";
92 bool notInIsolatedBrowser
= isolatedBrowserFlag
== "f";
93 if (!inIsolatedMozBrowser
&& !notInIsolatedBrowser
) {
97 // Should be followed by a colon.
98 if (!CheckChar(':')) {
102 // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
103 // from it and take the rest as the origin key.
105 // If the profile went through schema 1 -> schema 0 -> schema 1 switching
106 // we may have stored the full attributes origin suffix when there were
107 // more than just appId and inIsolatedMozBrowser set on storage principal's
110 // To preserve full uniqueness we store this suffix to the scope key.
111 // Schema 0 code will just ignore it while keeping the scoping unique.
113 // The whole scope string is in one of the following forms (when we are
116 // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
117 // "1001:f:gro.allizom.rxd.:https:443"
119 // +- the parser cursor position.
121 // If there is '^', the full origin attributes suffix follows. We search
122 // for ':' since it is the delimiter used in the scope string and is never
123 // contained in the origin attributes suffix. Remaining string after
124 // the comma is the reversed-domain+schema+port tuple.
126 if (CheckChar('^')) {
129 if (t
.Equals(Token::Char(':'))) {
135 StorageOriginAttributes
originAttributes(inIsolatedMozBrowser
);
136 originAttributes
.CreateSuffix(suffix
);
139 // Consume the rest of the input as "origin".
140 origin
.Assign(Substring(mCursor
, mEnd
));
144 class GetOriginParticular final
: public mozIStorageFunction
{
146 enum EParticular
{ ORIGIN_ATTRIBUTES_SUFFIX
, ORIGIN_KEY
};
148 explicit GetOriginParticular(EParticular aParticular
)
149 : mParticular(aParticular
) {}
152 GetOriginParticular() = delete;
153 ~GetOriginParticular() = default;
155 EParticular mParticular
;
158 NS_DECL_MOZISTORAGEFUNCTION
161 NS_IMPL_ISUPPORTS(GetOriginParticular
, mozIStorageFunction
)
164 GetOriginParticular::OnFunctionCall(mozIStorageValueArray
* aFunctionArguments
,
165 nsIVariant
** aResult
) {
169 rv
= aFunctionArguments
->GetUTF8String(0, scope
);
170 NS_ENSURE_SUCCESS(rv
, rv
);
172 nsAutoCString suffix
, origin
;
173 ExtractOriginData
extractor(scope
, suffix
, origin
);
175 nsCOMPtr
<nsIWritableVariant
> outVar(new nsVariant());
177 switch (mParticular
) {
178 case EParticular::ORIGIN_ATTRIBUTES_SUFFIX
:
179 rv
= outVar
->SetAsAUTF8String(suffix
);
181 case EParticular::ORIGIN_KEY
:
182 rv
= outVar
->SetAsAUTF8String(origin
);
186 NS_ENSURE_SUCCESS(rv
, rv
);
188 outVar
.forget(aResult
);
192 class StripOriginAddonId final
: public mozIStorageFunction
{
194 explicit StripOriginAddonId() = default;
197 ~StripOriginAddonId() = default;
200 NS_DECL_MOZISTORAGEFUNCTION
203 NS_IMPL_ISUPPORTS(StripOriginAddonId
, mozIStorageFunction
)
206 StripOriginAddonId::OnFunctionCall(mozIStorageValueArray
* aFunctionArguments
,
207 nsIVariant
** aResult
) {
210 nsAutoCString suffix
;
211 rv
= aFunctionArguments
->GetUTF8String(0, suffix
);
212 NS_ENSURE_SUCCESS(rv
, rv
);
214 // Deserialize and re-serialize to automatically drop any obsolete origin
217 bool ok
= oa
.PopulateFromSuffix(suffix
);
218 NS_ENSURE_TRUE(ok
, NS_ERROR_FAILURE
);
220 nsAutoCString newSuffix
;
221 oa
.CreateSuffix(newSuffix
);
223 nsCOMPtr
<nsIWritableVariant
> outVar
= new nsVariant();
224 rv
= outVar
->SetAsAUTF8String(newSuffix
);
225 NS_ENSURE_SUCCESS(rv
, rv
);
227 outVar
.forget(aResult
);
231 nsresult
CreateSchema1Tables(mozIStorageConnection
* aWorkerConnection
) {
234 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
235 "CREATE TABLE IF NOT EXISTS webappsstore2 ("
236 "originAttributes TEXT, "
238 "scope TEXT, " // Only for schema0 downgrade compatibility
241 NS_ENSURE_SUCCESS(rv
, rv
);
243 rv
= aWorkerConnection
->ExecuteSimpleSQL(
244 nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
245 " ON webappsstore2(originAttributes, originKey, key)"));
246 NS_ENSURE_SUCCESS(rv
, rv
);
251 nsresult
TablesExist(mozIStorageConnection
* aWorkerConnection
,
252 bool* aWebappsstore2Exists
, bool* aWebappsstoreExists
,
253 bool* aMoz_webappsstoreExists
) {
255 aWorkerConnection
->TableExists("webappsstore2"_ns
, aWebappsstore2Exists
);
256 NS_ENSURE_SUCCESS(rv
, rv
);
257 rv
= aWorkerConnection
->TableExists("webappsstore"_ns
, aWebappsstoreExists
);
258 NS_ENSURE_SUCCESS(rv
, rv
);
259 rv
= aWorkerConnection
->TableExists("moz_webappsstore"_ns
,
260 aMoz_webappsstoreExists
);
261 NS_ENSURE_SUCCESS(rv
, rv
);
266 nsresult
CreateCurrentSchemaOnEmptyTableInternal(
267 mozIStorageConnection
* aWorkerConnection
) {
268 nsresult rv
= CreateSchema1Tables(aWorkerConnection
);
269 NS_ENSURE_SUCCESS(rv
, rv
);
271 rv
= aWorkerConnection
->SetSchemaVersion(CURRENT_SCHEMA_VERSION
);
272 NS_ENSURE_SUCCESS(rv
, rv
);
279 namespace StorageDBUpdater
{
281 nsresult
CreateCurrentSchema(mozIStorageConnection
* aConnection
) {
282 mozStorageTransaction
transaction(aConnection
, false);
284 nsresult rv
= transaction
.Start();
285 NS_ENSURE_SUCCESS(rv
, rv
);
287 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
290 nsresult rv
= aConnection
->GetSchemaVersion(&schemaVer
);
291 NS_ENSURE_SUCCESS(rv
, rv
);
293 MOZ_DIAGNOSTIC_ASSERT(0 == schemaVer
);
295 bool webappsstore2Exists
, webappsstoreExists
, moz_webappsstoreExists
;
296 rv
= TablesExist(aConnection
, &webappsstore2Exists
, &webappsstoreExists
,
297 &moz_webappsstoreExists
);
298 NS_ENSURE_SUCCESS(rv
, rv
);
300 MOZ_DIAGNOSTIC_ASSERT(!webappsstore2Exists
&& !webappsstoreExists
&&
301 !moz_webappsstoreExists
);
305 rv
= CreateCurrentSchemaOnEmptyTableInternal(aConnection
);
306 NS_ENSURE_SUCCESS(rv
, rv
);
308 rv
= transaction
.Commit();
309 NS_ENSURE_SUCCESS(rv
, rv
);
314 nsresult
Update(mozIStorageConnection
* aWorkerConnection
) {
315 mozStorageTransaction
transaction(aWorkerConnection
, false);
317 nsresult rv
= transaction
.Start();
318 NS_ENSURE_SUCCESS(rv
, rv
);
320 bool doVacuum
= false;
323 rv
= aWorkerConnection
->GetSchemaVersion(&schemaVer
);
324 NS_ENSURE_SUCCESS(rv
, rv
);
326 // downgrade (v0) -> upgrade (v1+) specific code
327 if (schemaVer
>= 1) {
328 bool schema0IndexExists
;
329 rv
= aWorkerConnection
->IndexExists("scope_key_index"_ns
,
330 &schema0IndexExists
);
331 NS_ENSURE_SUCCESS(rv
, rv
);
333 if (schema0IndexExists
) {
334 // If this index exists, the database (already updated to schema >1)
335 // has been run again on schema 0 code. That recreated that index
336 // and might store some new rows while updating only the 'scope' column.
337 // For such added rows we must fill the new 'origin*' columns correctly
338 // otherwise there would be a data loss. The safest way to do it is to
339 // simply run the whole update to schema 1 again.
346 bool webappsstore2Exists
, webappsstoreExists
, moz_webappsstoreExists
;
347 rv
= TablesExist(aWorkerConnection
, &webappsstore2Exists
,
348 &webappsstoreExists
, &moz_webappsstoreExists
);
349 NS_ENSURE_SUCCESS(rv
, rv
);
351 if (!webappsstore2Exists
&& !webappsstoreExists
&&
352 !moz_webappsstoreExists
) {
353 // The database is empty, this is the first start. Just create the
354 // schema table and break to the next version to update to, i.e. bypass
355 // update from the old version.
357 // XXX What does "break to the next version to update to" mean here? It
358 // seems to refer to the 'break' statement below, but that breaks out of
359 // the 'switch' statement and continues with committing the transaction.
360 // Either this is wrong, or the comment above is misleading.
362 rv
= CreateCurrentSchemaOnEmptyTableInternal(aWorkerConnection
);
363 NS_ENSURE_SUCCESS(rv
, rv
);
370 // Ensure Gecko 1.9.1 storage table
371 rv
= aWorkerConnection
->ExecuteSimpleSQL(
372 nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
378 NS_ENSURE_SUCCESS(rv
, rv
);
380 rv
= aWorkerConnection
->ExecuteSimpleSQL(
381 nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
382 " ON webappsstore2(scope, key)"));
383 NS_ENSURE_SUCCESS(rv
, rv
);
385 nsCOMPtr
<mozIStorageFunction
> function1(new nsReverseStringSQLFunction());
386 NS_ENSURE_TRUE(function1
, NS_ERROR_OUT_OF_MEMORY
);
388 rv
= aWorkerConnection
->CreateFunction("REVERSESTRING"_ns
, 1, function1
);
389 NS_ENSURE_SUCCESS(rv
, rv
);
391 // Check if there is storage of Gecko 1.9.0 and if so, upgrade that
392 // storage to actual webappsstore2 table and drop the obsolete table.
393 // First process this newer table upgrade to priority potential duplicates
394 // from older storage table.
395 if (webappsstoreExists
) {
396 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
397 "INSERT OR IGNORE INTO "
398 "webappsstore2(scope, key, value, secure, owner) "
399 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
400 "FROM webappsstore"));
401 NS_ENSURE_SUCCESS(rv
, rv
);
403 rv
= aWorkerConnection
->ExecuteSimpleSQL("DROP TABLE webappsstore"_ns
);
404 NS_ENSURE_SUCCESS(rv
, rv
);
407 // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
408 // to actual webappsstore2 table and drop the obsolete table. Potential
409 // duplicates will be ignored.
410 if (moz_webappsstoreExists
) {
411 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
412 "INSERT OR IGNORE INTO "
413 "webappsstore2(scope, key, value, secure, owner) "
414 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
415 "FROM moz_webappsstore"));
416 NS_ENSURE_SUCCESS(rv
, rv
);
418 rv
= aWorkerConnection
->ExecuteSimpleSQL(
419 "DROP TABLE moz_webappsstore"_ns
);
420 NS_ENSURE_SUCCESS(rv
, rv
);
423 aWorkerConnection
->RemoveFunction("REVERSESTRING"_ns
);
425 // Update the scoping to match the new implememntation: split to oa suffix
426 // and origin key First rename the old table, we want to remove some
427 // columns no longer needed, but even before that drop all indexes from it
428 // (CREATE IF NOT EXISTS for index on the new table would falsely find the
430 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
431 "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
432 NS_ENSURE_SUCCESS(rv
, rv
);
434 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
435 "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
436 NS_ENSURE_SUCCESS(rv
, rv
);
438 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
439 "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
440 NS_ENSURE_SUCCESS(rv
, rv
);
442 nsCOMPtr
<mozIStorageFunction
> oaSuffixFunc(new GetOriginParticular(
443 GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX
));
444 rv
= aWorkerConnection
->CreateFunction("GET_ORIGIN_SUFFIX"_ns
, 1,
446 NS_ENSURE_SUCCESS(rv
, rv
);
448 nsCOMPtr
<mozIStorageFunction
> originKeyFunc(
449 new GetOriginParticular(GetOriginParticular::ORIGIN_KEY
));
450 rv
= aWorkerConnection
->CreateFunction("GET_ORIGIN_KEY"_ns
, 1,
452 NS_ENSURE_SUCCESS(rv
, rv
);
454 // Here we ensure this schema tables when we are updating.
455 rv
= CreateSchema1Tables(aWorkerConnection
);
456 NS_ENSURE_SUCCESS(rv
, rv
);
458 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
459 "INSERT OR IGNORE INTO "
460 "webappsstore2 (originAttributes, originKey, scope, key, value) "
461 "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, "
463 "FROM webappsstore2_old"));
464 NS_ENSURE_SUCCESS(rv
, rv
);
466 rv
= aWorkerConnection
->ExecuteSimpleSQL(
467 "DROP TABLE webappsstore2_old"_ns
);
468 NS_ENSURE_SUCCESS(rv
, rv
);
470 aWorkerConnection
->RemoveFunction("GET_ORIGIN_SUFFIX"_ns
);
471 aWorkerConnection
->RemoveFunction("GET_ORIGIN_KEY"_ns
);
473 rv
= aWorkerConnection
->SetSchemaVersion(1);
474 NS_ENSURE_SUCCESS(rv
, rv
);
479 nsCOMPtr
<mozIStorageFunction
> oaStripAddonId(new StripOriginAddonId());
480 rv
= aWorkerConnection
->CreateFunction("STRIP_ADDON_ID"_ns
, 1,
482 NS_ENSURE_SUCCESS(rv
, rv
);
484 rv
= aWorkerConnection
->ExecuteSimpleSQL(nsLiteralCString(
485 "UPDATE webappsstore2 "
486 "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
487 "WHERE originAttributes LIKE '^%'"));
488 NS_ENSURE_SUCCESS(rv
, rv
);
490 aWorkerConnection
->RemoveFunction("STRIP_ADDON_ID"_ns
);
492 rv
= aWorkerConnection
->SetSchemaVersion(2);
493 NS_ENSURE_SUCCESS(rv
, rv
);
497 case CURRENT_SCHEMA_VERSION
:
498 // Ensure the tables and indexes are up. This is mostly a no-op
499 // in common scenarios.
500 rv
= CreateSchema1Tables(aWorkerConnection
);
501 NS_ENSURE_SUCCESS(rv
, rv
);
503 // Nothing more to do here, this is the current schema version
511 rv
= transaction
.Commit();
512 NS_ENSURE_SUCCESS(rv
, rv
);
515 // In some cases this can make the disk file of the database significantly
516 // smaller. VACUUM cannot be executed inside a transaction.
517 rv
= aWorkerConnection
->ExecuteSimpleSQL("VACUUM"_ns
);
518 NS_ENSURE_SUCCESS(rv
, rv
);
524 } // namespace StorageDBUpdater
525 } // namespace mozilla::dom