blob: 3f2c814a1833549a17beb38845f0094a098841e1 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_
#define NET_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_
#include <memory>
#include <optional>
#include <string>
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/types/expected.h"
#include "base/types/pass_key.h"
#include "components/sqlite_vfs/sqlite_sandboxed_vfs.h"
#include "components/sqlite_vfs/vfs_utils.h"
#include "net/base/io_buffer.h"
#include "net/base/net_export.h"
#include "net/disk_cache/sql/cache_entry_key.h"
#include "net/disk_cache/sql/sql_backend_aliases.h"
#include "net/disk_cache/sql/sql_persistent_store.h"
#include "net/disk_cache/sql/sql_read_cache_memory_monitor.h"
#include "net/disk_cache/sql/sql_shared_cache_blob_handle.h"
#include "sql/database.h"
#include "sql/streaming_blob_handle.h"
namespace disk_cache {
class SqlSharedCacheIsolatedDatabaseTest;
// Manages a single SQLite database shard for a specific Network Isolation Key
// (NIK).
//
// To prevent cross-site data leaks, each NIK is assigned an isolated database
// via sqlite_vfs::SandboxedVfs. In the future, a read-only PendingFileSet
// corresponding to this database will be sent to the renderer process,
// allowing direct read access to the cached resources from the renderer.
// This class orchestrates the underlying SQLite database lifecycle, initializes
// the schema and metadata tables, and automatically razes and recreates the
// database upon NIK mismatch or schema version incompatibility.
class NET_EXPORT_PRIVATE SqlSharedCacheIsolatedDatabase {
public:
friend class SqlSharedCacheIsolatedDatabaseTest;
enum class Error {
kFailedForTesting = 1,
kFailedToOpenVfsFileSet = 2,
kFailedToOpenDatabase = 3,
kFailedToRazeIncompatibleVersionDatabase = 4,
kFailedToRazeIncompatibleNikDatabase = 5,
kFailedToStartTransaction = 6,
kFailedToCreateResourcesTable = 7,
kFailedToCreateResourcesTableIndex = 8,
kFailedToInitMetaTable = 9,
kFailedToSetNikInMetaTable = 10,
kFailedToCommitTransaction = 11,
kDatabaseNotOpen = 12,
kFailedToExecuteStatement = 13,
kEntryNotFound = 14,
kFailedToGetBlob = 15,
kFailedToWriteBlob = 16,
kFailedToSetReady = 17,
kBodyTooLarge = 18,
kInvalidWriteRange = 19,
kInvalidReadRange = 20,
kFailedToReadBlob = 21,
kFailedToShareConnection = 22,
kIsolatedDatabaseNotAvailable = 23,
kBodySizeMismatch = 24,
};
using ReadResult = SqlPersistentStore::ReadResult;
using ReadResultOrError = base::expected<ReadResult, Error>;
SqlSharedCacheIsolatedDatabase(
std::string nik_string,
const base::FilePath& directory,
SqlSharedCacheDbId shared_cache_db_id,
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor =
nullptr);
~SqlSharedCacheIsolatedDatabase();
// Returns the hashes of all ready resource URLs stored in this isolated
// database.
base::expected<std::vector<uint32_t>, Error> GetAllUrlHashes();
// Returns a PendingFileSet that represents a read-only connection to the
// database. This PendingFileSet can be passed to another process (like a
// sandboxed renderer) to allow it to read from the database directly.
base::expected<sqlite_vfs::PendingFileSet, Error>
GetSharedReadOnlyConnection();
base::expected<void, Error> Init();
// Inserts a new resource with the serialized headers and total body size.
// `total_body_size` is restricted by the maximum value of int32_t.
// The entry is immediately marked as `ready` if `total_body_size` is 0, or if
// `body` is provided and its size matches `total_body_size`.
// Otherwise, the entry is not ready and subsequent calls to `WriteBody` are
// required to append the body data. Finally, a call to `WriteBody` with
// `set_ready = true` makes it `ready` and accessible via `Read`.
base::expected<SqlSharedCacheRowId, Error> Insert(
const CacheEntryKey& entry_key,
scoped_refptr<net::IOBuffer> headers,
uint32_t total_body_size,
scoped_refptr<net::IOBuffer> body);
// Appends data from `buffer` (at the given `offset`) to the entry's
// body. If `set_ready` is true, the entry is marked as `ready` and will
// become accessible to `Read`.
base::expected<void, Error> WriteBody(const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int offset,
scoped_refptr<net::IOBuffer> buffer,
bool set_ready);
// Reads data from the entry's body into `buffer` starting at `offset`. The
// total body size must be specified in `body_size` to validate that the read
// range (`offset` + `buffer->size()`) does not exceed `body_size`. The entry
// must be in the `ready` state, otherwise this operation will fail.
ReadResultOrError Read(const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size,
int offset,
scoped_refptr<net::IOBuffer> buffer);
// Returns a handle to the entry's body blob, allowing streaming reads.
// The entry must be in the `ready` state, otherwise this operation will fail
// and return an Error.
base::expected<scoped_refptr<SqlSharedCacheBlobHandle>, Error> GetBlobHandle(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size);
// Deletes an entry by its row ID. Used for cleaning up partial entries.
void DeleteEntry(SqlSharedCacheRowId shared_cache_row_id);
// Deletes entries specified by `shared_cache_row_ids` from the database.
base::expected<void, Error> DeleteEntries(
const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids);
// Releases `db_assets_`.
void Cleanup();
enum class OperationForTesting {
kInit,
kInsert,
kWriteBody,
kRead,
kDeleteEntry,
kDeleteEntries,
};
using SimFailedCallback =
base::RepeatingCallback<bool(OperationForTesting op)>;
void SetSimulateDbFailureCallbackForTesting(SimFailedCallback callback);
bool HasRowForTesting(SqlSharedCacheRowId shared_cache_row_id);
private:
class DatabaseAssets {
public:
static std::unique_ptr<DatabaseAssets> MaybeCreate(
const base::FilePath& directory,
SqlSharedCacheDbId shared_cache_db_id);
DatabaseAssets(const base::FilePath& directory,
SqlSharedCacheDbId shared_cache_db_id,
sqlite_vfs::SqliteVfsFileSet vfs_file_set,
base::PassKey<DatabaseAssets>);
DatabaseAssets(const DatabaseAssets&) = delete;
DatabaseAssets& operator=(const DatabaseAssets&) = delete;
~DatabaseAssets();
sql::Database& db() { return db_; }
base::FilePath GetDbVirtualFilePath() const;
base::expected<sqlite_vfs::PendingFileSet, sqlite_vfs::FileSetError>
ShareConnection();
void AbandonAndDeleteFiles();
private:
const base::FilePath directory_;
const SqlSharedCacheDbId shared_cache_db_id_;
sqlite_vfs::SqliteVfsFileSet vfs_file_set_;
sqlite_vfs::SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_;
sql::Database db_;
};
class SqlSharedCacheBlobHandleImpl : public SqlSharedCacheBlobHandle {
public:
SqlSharedCacheBlobHandleImpl(
base::OnceClosure decrement_closure,
scoped_refptr<base::SequencedTaskRunner> task_runner);
private:
~SqlSharedCacheBlobHandleImpl() override;
base::OnceClosure decrement_closure_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
};
class BlobHandleHolder {
public:
BlobHandleHolder(sql::StreamingBlobHandle blob_handle,
const CacheEntryKey& entry_key,
int body_size);
~BlobHandleHolder();
BlobHandleHolder(const BlobHandleHolder&) = delete;
BlobHandleHolder& operator=(const BlobHandleHolder&) = delete;
sql::StreamingBlobHandle& blob_handle() { return blob_handle_; }
void IncrementRefCount() { ++ref_count_; }
void DecrementRefCount() { --ref_count_; }
size_t ref_count() const { return ref_count_; }
const std::string_view resource_url() const {
return entry_key_.resource_url();
}
int body_size() const { return body_size_; }
private:
sql::StreamingBlobHandle blob_handle_;
const CacheEntryKey entry_key_;
const int body_size_;
size_t ref_count_ = 1;
};
void DecrementBlobHandleRefCount(SqlSharedCacheRowId shared_cache_row_id);
base::expected<BlobHandleHolder*, Error> GetCachedBlobHandleHolder(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size);
base::expected<sql::StreamingBlobHandle, Error> GetStreamingBlobHandle(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int body_size);
base::expected<void, Error> WriteBodyInternal(
const CacheEntryKey& entry_key,
SqlSharedCacheRowId shared_cache_row_id,
int offset,
scoped_refptr<net::IOBuffer> buffer,
bool set_ready,
bool in_transaction);
bool ShouldSimulateFailure(OperationForTesting op) const;
std::string nik_string_;
std::unique_ptr<DatabaseAssets> db_assets_;
SimFailedCallback simulate_db_failure_callback_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor_;
absl::flat_hash_map<SqlSharedCacheRowId, std::unique_ptr<BlobHandleHolder>>
blob_handle_holders_;
base::WeakPtrFactory<SqlSharedCacheIsolatedDatabase> weak_factory_{this};
};
} // namespace disk_cache
#endif // NET_DISK_CACHE_SQL_SQL_SHARED_CACHE_ISOLATED_DATABASE_H_