tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

StorageDBUpdater.cpp (17286B)


      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/. */
      6 
      7 #include "LocalStorageManager.h"
      8 #include "StorageUtils.h"
      9 #include "mozIStorageBindingParams.h"
     10 #include "mozIStorageConnection.h"
     11 #include "mozIStorageFunction.h"
     12 #include "mozIStorageValueArray.h"
     13 #include "mozStorageHelper.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/StorageOriginAttributes.h"
     16 #include "mozilla/Tokenizer.h"
     17 #include "nsVariant.h"
     18 
     19 // Current version of the database schema
     20 #define CURRENT_SCHEMA_VERSION 2
     21 
     22 namespace mozilla::dom {
     23 
     24 using namespace StorageUtils;
     25 
     26 namespace {
     27 
     28 class nsReverseStringSQLFunction final : public mozIStorageFunction {
     29  ~nsReverseStringSQLFunction() = default;
     30 
     31  NS_DECL_ISUPPORTS
     32  NS_DECL_MOZISTORAGEFUNCTION
     33 };
     34 
     35 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
     36 
     37 NS_IMETHODIMP
     38 nsReverseStringSQLFunction::OnFunctionCall(
     39    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
     40  nsresult rv;
     41 
     42  nsAutoCString stringToReverse;
     43  rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
     44  NS_ENSURE_SUCCESS(rv, rv);
     45 
     46  nsAutoCString result;
     47  ReverseString(stringToReverse, result);
     48 
     49  RefPtr<nsVariant> outVar(new nsVariant());
     50  rv = outVar->SetAsAUTF8String(result);
     51  NS_ENSURE_SUCCESS(rv, rv);
     52 
     53  outVar.forget(aResult);
     54  return NS_OK;
     55 }
     56 
     57 // "scope" to "origin attributes suffix" and "origin key" convertor
     58 
     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;
     65 
     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);
     71 
     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;
     77    }
     78 
     79    // Should be followed by a colon.
     80    if (!CheckChar(':')) {
     81      return;
     82    }
     83 
     84    // Bail out if it isn't 'isolatedBrowserFlag'.
     85    nsDependentCSubstring isolatedBrowserFlag;
     86    if (!ReadWord(isolatedBrowserFlag)) {
     87      return;
     88    }
     89 
     90    bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
     91    bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
     92    if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
     93      return;
     94    }
     95 
     96    // Should be followed by a colon.
     97    if (!CheckChar(':')) {
     98      return;
     99    }
    100 
    101    // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
    102    // from it and take the rest as the origin key.
    103 
    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.
    108    //
    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.
    111    //
    112    // The whole scope string is in one of the following forms (when we are
    113    // here):
    114    //
    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.
    119    //
    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;
    131        }
    132      }
    133    } else {
    134      StorageOriginAttributes originAttributes(inIsolatedMozBrowser);
    135      originAttributes.CreateSuffix(suffix);
    136    }
    137 
    138    // Consume the rest of the input as "origin".
    139    origin.Assign(Substring(mCursor, mEnd));
    140  }
    141 };
    142 
    143 class GetOriginParticular final : public mozIStorageFunction {
    144 public:
    145  enum EParticular { ORIGIN_ATTRIBUTES_SUFFIX, ORIGIN_KEY };
    146 
    147  explicit GetOriginParticular(EParticular aParticular)
    148      : mParticular(aParticular) {}
    149 
    150 private:
    151  GetOriginParticular() = delete;
    152  ~GetOriginParticular() = default;
    153 
    154  EParticular mParticular;
    155 
    156  NS_DECL_ISUPPORTS
    157  NS_DECL_MOZISTORAGEFUNCTION
    158 };
    159 
    160 NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
    161 
    162 NS_IMETHODIMP
    163 GetOriginParticular::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
    164                                    nsIVariant** aResult) {
    165  nsresult rv;
    166 
    167  nsAutoCString scope;
    168  rv = aFunctionArguments->GetUTF8String(0, scope);
    169  NS_ENSURE_SUCCESS(rv, rv);
    170 
    171  nsAutoCString suffix, origin;
    172  ExtractOriginData extractor(scope, suffix, origin);
    173 
    174  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
    175 
    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;
    183  }
    184 
    185  NS_ENSURE_SUCCESS(rv, rv);
    186 
    187  outVar.forget(aResult);
    188  return NS_OK;
    189 }
    190 
    191 class StripOriginAddonId final : public mozIStorageFunction {
    192 public:
    193  explicit StripOriginAddonId() = default;
    194 
    195 private:
    196  ~StripOriginAddonId() = default;
    197 
    198  NS_DECL_ISUPPORTS
    199  NS_DECL_MOZISTORAGEFUNCTION
    200 };
    201 
    202 NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
    203 
    204 NS_IMETHODIMP
    205 StripOriginAddonId::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
    206                                   nsIVariant** aResult) {
    207  nsresult rv;
    208 
    209  nsAutoCString suffix;
    210  rv = aFunctionArguments->GetUTF8String(0, suffix);
    211  NS_ENSURE_SUCCESS(rv, rv);
    212 
    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);
    218 
    219  nsAutoCString newSuffix;
    220  oa.CreateSuffix(newSuffix);
    221 
    222  nsCOMPtr<nsIWritableVariant> outVar = new nsVariant();
    223  rv = outVar->SetAsAUTF8String(newSuffix);
    224  NS_ENSURE_SUCCESS(rv, rv);
    225 
    226  outVar.forget(aResult);
    227  return NS_OK;
    228 }
    229 
    230 nsresult CreateSchema1Tables(mozIStorageConnection* aWorkerConnection) {
    231  nsresult rv;
    232 
    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);
    241 
    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);
    246 
    247  return NS_OK;
    248 }
    249 
    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);
    261 
    262  return NS_OK;
    263 }
    264 
    265 nsresult CreateCurrentSchemaOnEmptyTableInternal(
    266    mozIStorageConnection* aWorkerConnection) {
    267  nsresult rv = CreateSchema1Tables(aWorkerConnection);
    268  NS_ENSURE_SUCCESS(rv, rv);
    269 
    270  rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
    271  NS_ENSURE_SUCCESS(rv, rv);
    272 
    273  return NS_OK;
    274 }
    275 
    276 }  // namespace
    277 
    278 namespace StorageDBUpdater {
    279 
    280 nsresult CreateCurrentSchema(mozIStorageConnection* aConnection) {
    281  mozStorageTransaction transaction(aConnection, false);
    282 
    283  nsresult rv = transaction.Start();
    284  NS_ENSURE_SUCCESS(rv, rv);
    285 
    286 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    287  {
    288    int32_t schemaVer;
    289    nsresult rv = aConnection->GetSchemaVersion(&schemaVer);
    290    NS_ENSURE_SUCCESS(rv, rv);
    291 
    292    MOZ_DIAGNOSTIC_ASSERT(0 == schemaVer);
    293 
    294    bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
    295    rv = TablesExist(aConnection, &webappsstore2Exists, &webappsstoreExists,
    296                     &moz_webappsstoreExists);
    297    NS_ENSURE_SUCCESS(rv, rv);
    298 
    299    MOZ_DIAGNOSTIC_ASSERT(!webappsstore2Exists && !webappsstoreExists &&
    300                          !moz_webappsstoreExists);
    301  }
    302 #endif
    303 
    304  rv = CreateCurrentSchemaOnEmptyTableInternal(aConnection);
    305  NS_ENSURE_SUCCESS(rv, rv);
    306 
    307  rv = transaction.Commit();
    308  NS_ENSURE_SUCCESS(rv, rv);
    309 
    310  return NS_OK;
    311 }
    312 
    313 nsresult Update(mozIStorageConnection* aWorkerConnection) {
    314  mozStorageTransaction transaction(aWorkerConnection, false);
    315 
    316  nsresult rv = transaction.Start();
    317  NS_ENSURE_SUCCESS(rv, rv);
    318 
    319  bool doVacuum = false;
    320 
    321  int32_t schemaVer;
    322  rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
    323  NS_ENSURE_SUCCESS(rv, rv);
    324 
    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);
    331 
    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;
    340    }
    341  }
    342 
    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);
    349 
    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.
    355 
    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.
    360 
    361        rv = CreateCurrentSchemaOnEmptyTableInternal(aWorkerConnection);
    362        NS_ENSURE_SUCCESS(rv, rv);
    363 
    364        break;
    365      }
    366 
    367      doVacuum = true;
    368 
    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);
    378 
    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);
    383 
    384      nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
    385      NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
    386 
    387      rv = aWorkerConnection->CreateFunction("REVERSESTRING"_ns, 1, function1);
    388      NS_ENSURE_SUCCESS(rv, rv);
    389 
    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);
    401 
    402        rv = aWorkerConnection->ExecuteSimpleSQL("DROP TABLE webappsstore"_ns);
    403        NS_ENSURE_SUCCESS(rv, rv);
    404      }
    405 
    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);
    416 
    417        rv = aWorkerConnection->ExecuteSimpleSQL(
    418            "DROP TABLE moz_webappsstore"_ns);
    419        NS_ENSURE_SUCCESS(rv, rv);
    420      }
    421 
    422      aWorkerConnection->RemoveFunction("REVERSESTRING"_ns);
    423 
    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);
    432 
    433      rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
    434          "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
    435      NS_ENSURE_SUCCESS(rv, rv);
    436 
    437      rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
    438          "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
    439      NS_ENSURE_SUCCESS(rv, rv);
    440 
    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);
    446 
    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);
    452 
    453      // Here we ensure this schema tables when we are updating.
    454      rv = CreateSchema1Tables(aWorkerConnection);
    455      NS_ENSURE_SUCCESS(rv, rv);
    456 
    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);
    464 
    465      rv = aWorkerConnection->ExecuteSimpleSQL(
    466          "DROP TABLE webappsstore2_old"_ns);
    467      NS_ENSURE_SUCCESS(rv, rv);
    468 
    469      aWorkerConnection->RemoveFunction("GET_ORIGIN_SUFFIX"_ns);
    470      aWorkerConnection->RemoveFunction("GET_ORIGIN_KEY"_ns);
    471 
    472      rv = aWorkerConnection->SetSchemaVersion(1);
    473      NS_ENSURE_SUCCESS(rv, rv);
    474 
    475      [[fallthrough]];
    476    }
    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);
    482 
    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);
    488 
    489      aWorkerConnection->RemoveFunction("STRIP_ADDON_ID"_ns);
    490 
    491      rv = aWorkerConnection->SetSchemaVersion(2);
    492      NS_ENSURE_SUCCESS(rv, rv);
    493 
    494      [[fallthrough]];
    495    }
    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);
    501 
    502      // Nothing more to do here, this is the current schema version
    503      break;
    504 
    505    default:
    506      MOZ_ASSERT(false);
    507      break;
    508  }  // switch
    509 
    510  rv = transaction.Commit();
    511  NS_ENSURE_SUCCESS(rv, rv);
    512 
    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);
    518  }
    519 
    520  return NS_OK;
    521 }
    522 
    523 }  // namespace StorageDBUpdater
    524 }  // namespace mozilla::dom