Bug 1861916 - Don't fail for Protocol attribute errors on Python < 3.10, r=Sasha
[gecko.git] / dom / storage / StorageDBUpdater.cpp
blob7dec6425e20c67bbecd752f30b6ad6947a397672
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;
26 namespace {
28 class nsReverseStringSQLFunction final : public mozIStorageFunction {
29 ~nsReverseStringSQLFunction() = default;
31 NS_DECL_ISUPPORTS
32 NS_DECL_MOZISTORAGEFUNCTION
35 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
37 NS_IMETHODIMP
38 nsReverseStringSQLFunction::OnFunctionCall(
39 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
40 nsresult rv;
42 nsAutoCString stringToReverse;
43 rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
44 NS_ENSURE_SUCCESS(rv, rv);
46 nsAutoCString result;
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);
54 return NS_OK;
57 // "scope" to "origin attributes suffix" and "origin key" convertor
59 class ExtractOriginData : protected mozilla::Tokenizer {
60 public:
61 ExtractOriginData(const nsACString& scope, nsACString& suffix,
62 nsACString& origin)
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
68 // is empty.
69 suffix.Truncate();
70 origin.Assign(scope);
72 // Bail out if it isn't appId.
73 // AppId doesn't exist any more but we could have old storage data...
74 uint32_t appId;
75 if (!ReadInteger(&appId)) {
76 return;
79 // Should be followed by a colon.
80 if (!CheckChar(':')) {
81 return;
84 // Bail out if it isn't 'isolatedBrowserFlag'.
85 nsDependentCSubstring isolatedBrowserFlag;
86 if (!ReadWord(isolatedBrowserFlag)) {
87 return;
90 bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
91 bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
92 if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
93 return;
96 // Should be followed by a colon.
97 if (!CheckChar(':')) {
98 return;
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
107 // OriginAttributes.
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
113 // here):
115 // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
116 // "1001:f:gro.allizom.rxd.:https:443"
117 // |
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.
124 Record();
125 if (CheckChar('^')) {
126 Token t;
127 while (Next(t)) {
128 if (t.Equals(Token::Char(':'))) {
129 Claim(suffix);
130 break;
133 } else {
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 {
144 public:
145 enum EParticular { ORIGIN_ATTRIBUTES_SUFFIX, ORIGIN_KEY };
147 explicit GetOriginParticular(EParticular aParticular)
148 : mParticular(aParticular) {}
150 private:
151 GetOriginParticular() = delete;
152 ~GetOriginParticular() = default;
154 EParticular mParticular;
156 NS_DECL_ISUPPORTS
157 NS_DECL_MOZISTORAGEFUNCTION
160 NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
162 NS_IMETHODIMP
163 GetOriginParticular::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
164 nsIVariant** aResult) {
165 nsresult rv;
167 nsAutoCString scope;
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);
179 break;
180 case EParticular::ORIGIN_KEY:
181 rv = outVar->SetAsAUTF8String(origin);
182 break;
185 NS_ENSURE_SUCCESS(rv, rv);
187 outVar.forget(aResult);
188 return NS_OK;
191 class StripOriginAddonId final : public mozIStorageFunction {
192 public:
193 explicit StripOriginAddonId() = default;
195 private:
196 ~StripOriginAddonId() = default;
198 NS_DECL_ISUPPORTS
199 NS_DECL_MOZISTORAGEFUNCTION
202 NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
204 NS_IMETHODIMP
205 StripOriginAddonId::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
206 nsIVariant** aResult) {
207 nsresult rv;
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
214 // attributes.
215 OriginAttributes oa;
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);
227 return NS_OK;
230 nsresult CreateSchema1Tables(mozIStorageConnection* aWorkerConnection) {
231 nsresult rv;
233 rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
234 "CREATE TABLE IF NOT EXISTS webappsstore2 ("
235 "originAttributes TEXT, "
236 "originKey TEXT, "
237 "scope TEXT, " // Only for schema0 downgrade compatibility
238 "key TEXT, "
239 "value TEXT)"));
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);
247 return NS_OK;
250 nsresult TablesExist(mozIStorageConnection* aWorkerConnection,
251 bool* aWebappsstore2Exists, bool* aWebappsstoreExists,
252 bool* aMoz_webappsstoreExists) {
253 nsresult rv =
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);
262 return NS_OK;
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);
273 return NS_OK;
276 } // namespace
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
288 int32_t schemaVer;
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);
302 #endif
304 rv = CreateCurrentSchemaOnEmptyTableInternal(aConnection);
305 NS_ENSURE_SUCCESS(rv, rv);
307 rv = transaction.Commit();
308 NS_ENSURE_SUCCESS(rv, rv);
310 return NS_OK;
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;
321 int32_t schemaVer;
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.
339 schemaVer = 0;
343 switch (schemaVer) {
344 case 0: {
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);
364 break;
367 doVacuum = true;
369 // Ensure Gecko 1.9.1 storage table
370 rv = aWorkerConnection->ExecuteSimpleSQL(
371 nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
372 "scope TEXT, "
373 "key TEXT, "
374 "value TEXT, "
375 "secure INTEGER, "
376 "owner TEXT)"));
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
428 // index!)
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,
444 oaSuffixFunc);
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,
450 originKeyFunc);
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, "
461 "value "
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);
475 [[fallthrough]];
477 case 1: {
478 nsCOMPtr<mozIStorageFunction> oaStripAddonId(new StripOriginAddonId());
479 rv = aWorkerConnection->CreateFunction("STRIP_ADDON_ID"_ns, 1,
480 oaStripAddonId);
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);
494 [[fallthrough]];
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
503 break;
505 default:
506 MOZ_ASSERT(false);
507 break;
508 } // switch
510 rv = transaction.Commit();
511 NS_ENSURE_SUCCESS(rv, rv);
513 if (doVacuum) {
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);
520 return NS_OK;
523 } // namespace StorageDBUpdater
524 } // namespace mozilla::dom