tor-browser

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

DBAction.cpp (8839B)


      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 "mozilla/dom/cache/DBAction.h"
      8 
      9 #include "mozIStorageConnection.h"
     10 #include "mozIStorageService.h"
     11 #include "mozStorageCID.h"
     12 #include "mozilla/AppShutdown.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/GeckoTrace.h"
     15 #include "mozilla/dom/cache/Connection.h"
     16 #include "mozilla/dom/cache/DBSchema.h"
     17 #include "mozilla/dom/cache/FileUtils.h"
     18 #include "mozilla/dom/cache/QuotaClient.h"
     19 #include "mozilla/dom/quota/PersistenceType.h"
     20 #include "mozilla/dom/quota/ResultExtensions.h"
     21 #include "mozilla/net/nsFileProtocolHandler.h"
     22 #include "nsIFile.h"
     23 #include "nsIFileURL.h"
     24 #include "nsIURI.h"
     25 #include "nsIURIMutator.h"
     26 
     27 namespace mozilla::dom::cache {
     28 
     29 using mozilla::dom::quota::CloneFileAndAppend;
     30 using mozilla::dom::quota::IsDatabaseCorruptionError;
     31 
     32 namespace {
     33 
     34 nsresult WipeDatabase(const CacheDirectoryMetadata& aDirectoryMetadata,
     35                      nsIFile& aDBFile) {
     36  QM_TRY_INSPECT(const auto& dbDir, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
     37                                        nsCOMPtr<nsIFile>, aDBFile, GetParent));
     38 
     39  QM_TRY(MOZ_TO_RESULT(RemoveNsIFile(aDirectoryMetadata, aDBFile)));
     40 
     41  // Note, the -wal journal file will be automatically deleted by sqlite when
     42  // the new database is created.  No need to explicitly delete it here.
     43 
     44  // Delete the morgue as well.
     45  QM_TRY(MOZ_TO_RESULT(BodyDeleteDir(aDirectoryMetadata, *dbDir)));
     46 
     47  QM_TRY(MOZ_TO_RESULT(WipePaddingFile(aDirectoryMetadata, dbDir)));
     48 
     49  return NS_OK;
     50 }
     51 
     52 }  // namespace
     53 
     54 DBAction::DBAction(Mode aMode) : mMode(aMode) {}
     55 
     56 DBAction::~DBAction() = default;
     57 
     58 void DBAction::RunOnTarget(
     59    SafeRefPtr<Resolver> aResolver,
     60    const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
     61    Data* aOptionalData, const Maybe<CipherKey>& aMaybeCipherKey) {
     62  MOZ_ASSERT(!NS_IsMainThread());
     63  MOZ_DIAGNOSTIC_ASSERT(aResolver);
     64  MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
     65  MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
     66 
     67  if (IsCanceled() || AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) {
     68    aResolver->Resolve(NS_ERROR_ABORT);
     69    return;
     70  }
     71 
     72  const auto resolveErr = [&aResolver](const nsresult rv) {
     73    aResolver->Resolve(rv);
     74  };
     75 
     76  QM_TRY_INSPECT(const auto& dbDir,
     77                 CloneFileAndAppend(*(aDirectoryMetadata->mDir), u"cache"_ns),
     78                 QM_VOID, resolveErr);
     79 
     80  nsCOMPtr<mozIStorageConnection> conn;
     81 
     82  // Attempt to reuse the connection opened by a previous Action.
     83  if (aOptionalData) {
     84    conn = aOptionalData->GetConnection();
     85  }
     86 
     87  // If there is no previous Action, then we must open one.
     88  if (!conn) {
     89    QM_TRY_UNWRAP(conn,
     90                  OpenConnection(*aDirectoryMetadata, *dbDir, aMaybeCipherKey),
     91                  QM_VOID, resolveErr);
     92    MOZ_DIAGNOSTIC_ASSERT(conn);
     93 
     94    // Save this connection in the shared Data object so later Actions can
     95    // use it.  This avoids opening a new connection for every Action.
     96    if (aOptionalData) {
     97      // Since we know this connection will be around for as long as the
     98      // Cache is open, use our special wrapped connection class.  This
     99      // will let us perform certain operations once the Cache origin
    100      // is closed.
    101      nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
    102      aOptionalData->SetConnection(wrapped);
    103    }
    104  }
    105 
    106  RunWithDBOnTarget(std::move(aResolver), *aDirectoryMetadata, dbDir, conn);
    107 }
    108 
    109 Result<nsCOMPtr<mozIStorageConnection>, nsresult> DBAction::OpenConnection(
    110    const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir,
    111    const Maybe<CipherKey>& aMaybeCipherKey) {
    112  MOZ_ASSERT(!NS_IsMainThread());
    113  MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= 0);
    114 
    115  QM_TRY_INSPECT(const bool& exists,
    116                 MOZ_TO_RESULT_INVOKE_MEMBER(aDBDir, Exists));
    117 
    118  if (!exists) {
    119    QM_TRY(OkIf(mMode == Create), Err(NS_ERROR_FILE_NOT_FOUND));
    120    QM_TRY(MOZ_TO_RESULT(aDBDir.Create(nsIFile::DIRECTORY_TYPE, 0755)));
    121  }
    122 
    123  QM_TRY_INSPECT(const auto& dbFile,
    124                 CloneFileAndAppend(aDBDir, kCachesSQLiteFilename));
    125 
    126  QM_TRY_RETURN(OpenDBConnection(aDirectoryMetadata, *dbFile, aMaybeCipherKey));
    127 }
    128 
    129 SyncDBAction::SyncDBAction(Mode aMode) : DBAction(aMode) {}
    130 
    131 SyncDBAction::~SyncDBAction() = default;
    132 
    133 void SyncDBAction::RunWithDBOnTarget(
    134    SafeRefPtr<Resolver> aResolver,
    135    const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
    136    mozIStorageConnection* aConn) {
    137  MOZ_ASSERT(!NS_IsMainThread());
    138  MOZ_DIAGNOSTIC_ASSERT(aResolver);
    139  MOZ_DIAGNOSTIC_ASSERT(aDBDir);
    140  MOZ_DIAGNOSTIC_ASSERT(aConn);
    141 
    142  nsresult rv = RunSyncWithDBOnTarget(aDirectoryMetadata, aDBDir, aConn);
    143  aResolver->Resolve(rv);
    144 }
    145 
    146 Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenDBConnection(
    147    const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile,
    148    const Maybe<CipherKey>& aMaybeCipherKey) {
    149  GECKO_TRACE_SCOPE("dom::cache", "OpenDBConnection");
    150 
    151  MOZ_ASSERT(!NS_IsMainThread());
    152  MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= -1);
    153  MOZ_DIAGNOSTIC_ASSERT_IF(aDirectoryMetadata.mIsPrivate, aMaybeCipherKey);
    154 
    155  // Use our default file:// protocol handler directly to construct the database
    156  // URL.  This avoids any problems if a plugin registers a custom file://
    157  // handler.  If such a custom handler used javascript, then we would have a
    158  // bad time running off the main thread here.
    159  auto handler = MakeRefPtr<nsFileProtocolHandler>();
    160  QM_TRY(MOZ_TO_RESULT(handler->Init()));
    161 
    162  QM_TRY_INSPECT(const auto& mutator, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    163                                          nsCOMPtr<nsIURIMutator>, handler,
    164                                          NewFileURIMutator, &aDBFile));
    165 
    166  const nsCString directoryLockIdClause =
    167      "&directoryLockId="_ns +
    168      IntToCString(aDirectoryMetadata.mDirectoryLockId);
    169 
    170  const auto keyClause = [&aMaybeCipherKey] {
    171    nsAutoCString keyClause;
    172    if (aMaybeCipherKey) {
    173      keyClause.AssignLiteral("&key=");
    174      for (uint8_t byte : CipherStrategy::SerializeKey(*aMaybeCipherKey)) {
    175        keyClause.AppendPrintf("%02x", byte);
    176      }
    177    }
    178    return keyClause;
    179  }();
    180 
    181  nsCOMPtr<nsIFileURL> dbFileUrl;
    182  QM_TRY(MOZ_TO_RESULT(
    183      NS_MutateURI(mutator)
    184          .SetQuery("cache=private"_ns + directoryLockIdClause + keyClause)
    185          .Finalize(dbFileUrl)));
    186 
    187  QM_TRY_INSPECT(const auto& storageService,
    188                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
    189                                         MOZ_SELECT_OVERLOAD(do_GetService),
    190                                         MOZ_STORAGE_SERVICE_CONTRACTID),
    191                 Err(NS_ERROR_UNEXPECTED));
    192 
    193  QM_TRY_UNWRAP(
    194      auto conn,
    195      QM_OR_ELSE_WARN_IF(
    196          // Expression.
    197          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    198              nsCOMPtr<mozIStorageConnection>, storageService,
    199              OpenDatabaseWithFileURL, dbFileUrl, ""_ns,
    200              mozIStorageService::CONNECTION_DEFAULT),
    201          // Predicate.
    202          IsDatabaseCorruptionError,
    203          // Fallback.
    204          ([&aDirectoryMetadata, &aDBFile, &storageService,
    205            &dbFileUrl](const nsresult rv)
    206               -> Result<nsCOMPtr<mozIStorageConnection>, nsresult> {
    207            NS_WARNING("Cache database corrupted. Recreating empty database.");
    208 
    209            // There is nothing else we can do to recover.  Also, this data
    210            // can be deleted by QuotaManager at any time anyways.
    211            QM_TRY(MOZ_TO_RESULT(WipeDatabase(aDirectoryMetadata, aDBFile)));
    212 
    213            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    214                nsCOMPtr<mozIStorageConnection>, storageService,
    215                OpenDatabaseWithFileURL, dbFileUrl, ""_ns,
    216                mozIStorageService::CONNECTION_DEFAULT));
    217          })));
    218 
    219  // Check the schema to make sure it is not too old.
    220  QM_TRY_INSPECT(const int32_t& schemaVersion,
    221                 MOZ_TO_RESULT_INVOKE_MEMBER(conn, GetSchemaVersion));
    222  if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
    223    // Close existing connection before wiping database.
    224    conn = nullptr;
    225 
    226    QM_TRY(MOZ_TO_RESULT(WipeDatabase(aDirectoryMetadata, aDBFile)));
    227 
    228    QM_TRY_UNWRAP(conn, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
    229                            nsCOMPtr<mozIStorageConnection>, storageService,
    230                            OpenDatabaseWithFileURL, dbFileUrl, ""_ns,
    231                            mozIStorageService::CONNECTION_DEFAULT));
    232  }
    233 
    234  QM_TRY(MOZ_TO_RESULT(db::InitializeConnection(*conn)));
    235 
    236  return conn;
    237 }
    238 
    239 }  // namespace mozilla::dom::cache