Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / storage / StorageDBUpdater.cpp
blob255deddb4d465a52dfd289a6330fb1697f586969
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;
27 namespace {
29 class nsReverseStringSQLFunction final : public mozIStorageFunction {
30 ~nsReverseStringSQLFunction() = default;
32 NS_DECL_ISUPPORTS
33 NS_DECL_MOZISTORAGEFUNCTION
36 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
38 NS_IMETHODIMP
39 nsReverseStringSQLFunction::OnFunctionCall(
40 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
41 nsresult rv;
43 nsAutoCString stringToReverse;
44 rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
45 NS_ENSURE_SUCCESS(rv, rv);
47 nsAutoCString result;
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);
55 return NS_OK;
58 // "scope" to "origin attributes suffix" and "origin key" convertor
60 class ExtractOriginData : protected mozilla::Tokenizer {
61 public:
62 ExtractOriginData(const nsACString& scope, nsACString& suffix,
63 nsACString& origin)
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
69 // is empty.
70 suffix.Truncate();
71 origin.Assign(scope);
73 // Bail out if it isn't appId.
74 // AppId doesn't exist any more but we could have old storage data...
75 uint32_t appId;
76 if (!ReadInteger(&appId)) {
77 return;
80 // Should be followed by a colon.
81 if (!CheckChar(':')) {
82 return;
85 // Bail out if it isn't 'isolatedBrowserFlag'.
86 nsDependentCSubstring isolatedBrowserFlag;
87 if (!ReadWord(isolatedBrowserFlag)) {
88 return;
91 bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
92 bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
93 if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
94 return;
97 // Should be followed by a colon.
98 if (!CheckChar(':')) {
99 return;
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
108 // OriginAttributes.
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
114 // here):
116 // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
117 // "1001:f:gro.allizom.rxd.:https:443"
118 // |
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.
125 Record();
126 if (CheckChar('^')) {
127 Token t;
128 while (Next(t)) {
129 if (t.Equals(Token::Char(':'))) {
130 Claim(suffix);
131 break;
134 } else {
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 {
145 public:
146 enum EParticular { ORIGIN_ATTRIBUTES_SUFFIX, ORIGIN_KEY };
148 explicit GetOriginParticular(EParticular aParticular)
149 : mParticular(aParticular) {}
151 private:
152 GetOriginParticular() = delete;
153 ~GetOriginParticular() = default;
155 EParticular mParticular;
157 NS_DECL_ISUPPORTS
158 NS_DECL_MOZISTORAGEFUNCTION
161 NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
163 NS_IMETHODIMP
164 GetOriginParticular::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
165 nsIVariant** aResult) {
166 nsresult rv;
168 nsAutoCString scope;
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);
180 break;
181 case EParticular::ORIGIN_KEY:
182 rv = outVar->SetAsAUTF8String(origin);
183 break;
186 NS_ENSURE_SUCCESS(rv, rv);
188 outVar.forget(aResult);
189 return NS_OK;
192 class StripOriginAddonId final : public mozIStorageFunction {
193 public:
194 explicit StripOriginAddonId() = default;
196 private:
197 ~StripOriginAddonId() = default;
199 NS_DECL_ISUPPORTS
200 NS_DECL_MOZISTORAGEFUNCTION
203 NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
205 NS_IMETHODIMP
206 StripOriginAddonId::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
207 nsIVariant** aResult) {
208 nsresult rv;
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
215 // attributes.
216 OriginAttributes oa;
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);
228 return NS_OK;
231 nsresult CreateSchema1Tables(mozIStorageConnection* aWorkerConnection) {
232 nsresult rv;
234 rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
235 "CREATE TABLE IF NOT EXISTS webappsstore2 ("
236 "originAttributes TEXT, "
237 "originKey TEXT, "
238 "scope TEXT, " // Only for schema0 downgrade compatibility
239 "key TEXT, "
240 "value TEXT)"));
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);
248 return NS_OK;
251 nsresult TablesExist(mozIStorageConnection* aWorkerConnection,
252 bool* aWebappsstore2Exists, bool* aWebappsstoreExists,
253 bool* aMoz_webappsstoreExists) {
254 nsresult rv =
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);
263 return NS_OK;
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);
274 return NS_OK;
277 } // namespace
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
289 int32_t schemaVer;
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);
303 #endif
305 rv = CreateCurrentSchemaOnEmptyTableInternal(aConnection);
306 NS_ENSURE_SUCCESS(rv, rv);
308 rv = transaction.Commit();
309 NS_ENSURE_SUCCESS(rv, rv);
311 return NS_OK;
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;
322 int32_t schemaVer;
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.
340 schemaVer = 0;
344 switch (schemaVer) {
345 case 0: {
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);
365 break;
368 doVacuum = true;
370 // Ensure Gecko 1.9.1 storage table
371 rv = aWorkerConnection->ExecuteSimpleSQL(
372 nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
373 "scope TEXT, "
374 "key TEXT, "
375 "value TEXT, "
376 "secure INTEGER, "
377 "owner TEXT)"));
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
429 // index!)
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,
445 oaSuffixFunc);
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,
451 originKeyFunc);
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, "
462 "value "
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);
476 [[fallthrough]];
478 case 1: {
479 nsCOMPtr<mozIStorageFunction> oaStripAddonId(new StripOriginAddonId());
480 rv = aWorkerConnection->CreateFunction("STRIP_ADDON_ID"_ns, 1,
481 oaStripAddonId);
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);
495 [[fallthrough]];
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
504 break;
506 default:
507 MOZ_ASSERT(false);
508 break;
509 } // switch
511 rv = transaction.Commit();
512 NS_ENSURE_SUCCESS(rv, rv);
514 if (doVacuum) {
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);
521 return NS_OK;
524 } // namespace StorageDBUpdater
525 } // namespace mozilla::dom