| // 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)>; | |
| static void SetGlobalSimulateDbFailureCallbackForTesting( | |
| SimFailedCallback callback); | |
| 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_ |