summaryrefslogtreecommitdiffstats
path: root/dom/storage/StorageDBUpdater.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/storage/StorageDBUpdater.cpp')
-rw-r--r--dom/storage/StorageDBUpdater.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/dom/storage/StorageDBUpdater.cpp b/dom/storage/StorageDBUpdater.cpp
new file mode 100644
index 0000000000..df74ac1da0
--- /dev/null
+++ b/dom/storage/StorageDBUpdater.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LocalStorageManager.h"
+#include "StorageUtils.h"
+
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsVariant.h"
+#include "mozilla/Tokenizer.h"
+#include "mozIStorageConnection.h"
+#include "mozStorageHelper.h"
+
+// Current version of the database schema
+#define CURRENT_SCHEMA_VERSION 2
+
+namespace mozilla {
+namespace dom {
+
+using namespace StorageUtils;
+
+namespace {
+
+class nsReverseStringSQLFunction final : public mozIStorageFunction {
+ ~nsReverseStringSQLFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+nsReverseStringSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
+ nsresult rv;
+
+ nsAutoCString stringToReverse;
+ rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString result;
+ ReverseString(stringToReverse, result);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsAUTF8String(result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+// "scope" to "origin attributes suffix" and "origin key" convertor
+
+class ExtractOriginData : protected mozilla::Tokenizer {
+ public:
+ ExtractOriginData(const nsACString& scope, nsACString& suffix,
+ nsACString& origin)
+ : mozilla::Tokenizer(scope) {
+ using mozilla::OriginAttributes;
+
+ // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
+ // we don't find it, the scope is our new origin key and suffix
+ // is empty.
+ suffix.Truncate();
+ origin.Assign(scope);
+
+ // Bail out if it isn't appId.
+ // AppId doesn't exist any more but we could have old storage data...
+ uint32_t appId;
+ if (!ReadInteger(&appId)) {
+ return;
+ }
+
+ // Should be followed by a colon.
+ if (!CheckChar(':')) {
+ return;
+ }
+
+ // Bail out if it isn't 'isolatedBrowserFlag'.
+ nsDependentCSubstring isolatedBrowserFlag;
+ if (!ReadWord(isolatedBrowserFlag)) {
+ return;
+ }
+
+ bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
+ bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
+ if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
+ return;
+ }
+
+ // Should be followed by a colon.
+ if (!CheckChar(':')) {
+ return;
+ }
+
+ // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
+ // from it and take the rest as the origin key.
+
+ // If the profile went through schema 1 -> schema 0 -> schema 1 switching
+ // we may have stored the full attributes origin suffix when there were
+ // more than just appId and inIsolatedMozBrowser set on storage principal's
+ // OriginAttributes.
+ //
+ // To preserve full uniqueness we store this suffix to the scope key.
+ // Schema 0 code will just ignore it while keeping the scoping unique.
+ //
+ // The whole scope string is in one of the following forms (when we are
+ // here):
+ //
+ // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
+ // "1001:f:gro.allizom.rxd.:https:443"
+ // |
+ // +- the parser cursor position.
+ //
+ // If there is '^', the full origin attributes suffix follows. We search
+ // for ':' since it is the delimiter used in the scope string and is never
+ // contained in the origin attributes suffix. Remaining string after
+ // the comma is the reversed-domain+schema+port tuple.
+ Record();
+ if (CheckChar('^')) {
+ Token t;
+ while (Next(t)) {
+ if (t.Equals(Token::Char(':'))) {
+ Claim(suffix);
+ break;
+ }
+ }
+ } else {
+ OriginAttributes attrs(inIsolatedMozBrowser);
+ attrs.CreateSuffix(suffix);
+ }
+
+ // Consume the rest of the input as "origin".
+ origin.Assign(Substring(mCursor, mEnd));
+ }
+};
+
+class GetOriginParticular final : public mozIStorageFunction {
+ public:
+ enum EParticular { ORIGIN_ATTRIBUTES_SUFFIX, ORIGIN_KEY };
+
+ explicit GetOriginParticular(EParticular aParticular)
+ : mParticular(aParticular) {}
+
+ private:
+ GetOriginParticular() = delete;
+ ~GetOriginParticular() = default;
+
+ EParticular mParticular;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
+
+NS_IMETHODIMP
+GetOriginParticular::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
+ nsIVariant** aResult) {
+ nsresult rv;
+
+ nsAutoCString scope;
+ rv = aFunctionArguments->GetUTF8String(0, scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix, origin;
+ ExtractOriginData extractor(scope, suffix, origin);
+
+ nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+ switch (mParticular) {
+ case EParticular::ORIGIN_ATTRIBUTES_SUFFIX:
+ rv = outVar->SetAsAUTF8String(suffix);
+ break;
+ case EParticular::ORIGIN_KEY:
+ rv = outVar->SetAsAUTF8String(origin);
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+class StripOriginAddonId final : public mozIStorageFunction {
+ public:
+ explicit StripOriginAddonId() = default;
+
+ private:
+ ~StripOriginAddonId() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
+
+NS_IMETHODIMP
+StripOriginAddonId::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
+ nsIVariant** aResult) {
+ nsresult rv;
+
+ nsAutoCString suffix;
+ rv = aFunctionArguments->GetUTF8String(0, suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Deserialize and re-serialize to automatically drop any obsolete origin
+ // attributes.
+ OriginAttributes oa;
+ bool ok = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+ nsAutoCString newSuffix;
+ oa.CreateSuffix(newSuffix);
+
+ nsCOMPtr<nsIWritableVariant> outVar = new nsVariant();
+ rv = outVar->SetAsAUTF8String(newSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace
+
+namespace StorageDBUpdater {
+
+nsresult CreateSchema1Tables(mozIStorageConnection* aWorkerConnection) {
+ nsresult rv;
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "CREATE TABLE IF NOT EXISTS webappsstore2 ("
+ "originAttributes TEXT, "
+ "originKey TEXT, "
+ "scope TEXT, " // Only for schema0 downgrade compatibility
+ "key TEXT, "
+ "value TEXT)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
+ " ON webappsstore2(originAttributes, originKey, key)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult Update(mozIStorageConnection* aWorkerConnection) {
+ nsresult rv;
+
+ mozStorageTransaction transaction(aWorkerConnection, false);
+
+ bool doVacuum = false;
+
+ int32_t schemaVer;
+ rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // downgrade (v0) -> upgrade (v1+) specific code
+ if (schemaVer >= 1) {
+ bool schema0IndexExists;
+ rv = aWorkerConnection->IndexExists("scope_key_index"_ns,
+ &schema0IndexExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (schema0IndexExists) {
+ // If this index exists, the database (already updated to schema >1)
+ // has been run again on schema 0 code. That recreated that index
+ // and might store some new rows while updating only the 'scope' column.
+ // For such added rows we must fill the new 'origin*' columns correctly
+ // otherwise there would be a data loss. The safest way to do it is to
+ // simply run the whole update to schema 1 again.
+ schemaVer = 0;
+ }
+ }
+
+ switch (schemaVer) {
+ case 0: {
+ bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
+
+ rv = aWorkerConnection->TableExists("webappsstore2"_ns,
+ &webappsstore2Exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists("webappsstore"_ns,
+ &webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists("moz_webappsstore"_ns,
+ &moz_webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!webappsstore2Exists && !webappsstoreExists &&
+ !moz_webappsstoreExists) {
+ // The database is empty, this is the first start. Just create the
+ // schema table and break to the next version to update to, i.e. bypass
+ // update from the old version.
+
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+
+ doVacuum = true;
+
+ // Ensure Gecko 1.9.1 storage table
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
+ "scope TEXT, "
+ "key TEXT, "
+ "value TEXT, "
+ "secure INTEGER, "
+ "owner TEXT)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
+ " ON webappsstore2(scope, key)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
+ NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = aWorkerConnection->CreateFunction("REVERSESTRING"_ns, 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if there is storage of Gecko 1.9.0 and if so, upgrade that
+ // storage to actual webappsstore2 table and drop the obsolete table.
+ // First process this newer table upgrade to priority potential duplicates
+ // from older storage table.
+ if (webappsstoreExists) {
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "INSERT OR IGNORE INTO "
+ "webappsstore2(scope, key, value, secure, owner) "
+ "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
+ "FROM webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL("DROP TABLE webappsstore"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
+ // to actual webappsstore2 table and drop the obsolete table. Potential
+ // duplicates will be ignored.
+ if (moz_webappsstoreExists) {
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "INSERT OR IGNORE INTO "
+ "webappsstore2(scope, key, value, secure, owner) "
+ "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
+ "FROM moz_webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ "DROP TABLE moz_webappsstore"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aWorkerConnection->RemoveFunction("REVERSESTRING"_ns);
+
+ // Update the scoping to match the new implememntation: split to oa suffix
+ // and origin key First rename the old table, we want to remove some
+ // columns no longer needed, but even before that drop all indexes from it
+ // (CREATE IF NOT EXISTS for index on the new table would falsely find the
+ // index!)
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> oaSuffixFunc(new GetOriginParticular(
+ GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
+ rv = aWorkerConnection->CreateFunction("GET_ORIGIN_SUFFIX"_ns, 1,
+ oaSuffixFunc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> originKeyFunc(
+ new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
+ rv = aWorkerConnection->CreateFunction("GET_ORIGIN_KEY"_ns, 1,
+ originKeyFunc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Here we ensure this schema tables when we are updating.
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "INSERT OR IGNORE INTO "
+ "webappsstore2 (originAttributes, originKey, scope, key, value) "
+ "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, "
+ "value "
+ "FROM webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ "DROP TABLE webappsstore2_old"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aWorkerConnection->RemoveFunction("GET_ORIGIN_SUFFIX"_ns);
+ aWorkerConnection->RemoveFunction("GET_ORIGIN_KEY"_ns);
+
+ rv = aWorkerConnection->SetSchemaVersion(1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ [[fallthrough]];
+ }
+ case 1: {
+ nsCOMPtr<mozIStorageFunction> oaStripAddonId(new StripOriginAddonId());
+ rv = aWorkerConnection->CreateFunction("STRIP_ADDON_ID"_ns, 1,
+ oaStripAddonId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "UPDATE webappsstore2 "
+ "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
+ "WHERE originAttributes LIKE '^%'"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aWorkerConnection->RemoveFunction("STRIP_ADDON_ID"_ns);
+
+ rv = aWorkerConnection->SetSchemaVersion(2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ [[fallthrough]];
+ }
+ case CURRENT_SCHEMA_VERSION:
+ // Ensure the tables and indexes are up. This is mostly a no-op
+ // in common scenarios.
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Nothing more to do here, this is the current schema version
+ break;
+
+ default:
+ MOZ_ASSERT(false);
+ break;
+ } // switch
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (doVacuum) {
+ // In some cases this can make the disk file of the database significantly
+ // smaller. VACUUM cannot be executed inside a transaction.
+ rv = aWorkerConnection->ExecuteSimpleSQL("VACUUM"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+} // namespace StorageDBUpdater
+} // namespace dom
+} // namespace mozilla