#include "net/http/http_cache.h"
#include <algorithm>
#include <optional>
#include <string_view>
#include <utility>
#include "base/byte_count.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/histogram_macros_local.h"
#include "base/numerics/safe_conversions.h"
#include "base/pickle.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "crypto/hash.h"
#include "http_request_info.h"
#include "net/base/cache_type.h"
#include "net/base/features.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_isolation_key.h"
#include "net/base/task/task_runner.h"
#include "net/base/upload_data_stream.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache_transaction.h"
#include "net/http/http_cache_writers.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_util.h"
#include "net/http/no_vary_search_cache_storage_file_operations.h"
#include "net/log/net_log_with_source.h"
#include "net/quic/quic_server_info.h"
#include "url/origin.h"
#if BUILDFLAG(IS_POSIX)
#include <unistd.h>
#endif
namespace net {
BASE_FEATURE(kHttpCacheInitializeDiskCacheBackendEarly,
base::FEATURE_DISABLED_BY_DEFAULT);
namespace {
bool g_init_cache = false;
bool g_enable_split_cache = false;
template <typename T>
RequestPriority GetHighestPriority(const T& transactions) {
RequestPriority highest = RequestPriority::IDLE;
for (const auto tx : transactions) {
highest = std::max(highest, tx->priority());
}
return highest;
}
const scoped_refptr<base::SingleThreadTaskRunner>& TaskRunner(
net::RequestPriority priority) {
if (features::kNetTaskSchedulerHttpCache.Get()) {
return net::GetTaskRunner(priority);
}
return base::SingleThreadTaskRunner::GetCurrentDefault();
}
}
const char HttpCache::kDoubleKeyPrefix[] = "_dk_";
const char HttpCache::kDoubleKeySeparator[] = " ";
const char HttpCache::kSubframeDocumentResourcePrefix[] = "s_";
const char HttpCache::kCrossSiteMainFrameNavigationPrefix[] = "cn_";
HttpCache::DefaultBackend::DefaultBackend(
CacheType type,
BackendType backend_type,
scoped_refptr<disk_cache::BackendFileOperationsFactory>
file_operations_factory,
const base::FilePath& path,
int max_bytes,
bool hard_reset)
: type_(type),
backend_type_(backend_type),
file_operations_factory_(std::move(file_operations_factory)),
path_(path),
max_bytes_(max_bytes),
hard_reset_(hard_reset) {}
HttpCache::DefaultBackend::~DefaultBackend() = default;
std::unique_ptr<HttpCache::BackendFactory> HttpCache::DefaultBackend::InMemory(
int max_bytes) {
return std::make_unique<DefaultBackend>(MEMORY_CACHE, CACHE_BACKEND_DEFAULT,
nullptr,
base::FilePath(), max_bytes, false);
}
disk_cache::BackendResult HttpCache::DefaultBackend::CreateBackend(
NetLog* net_log,
base::OnceCallback<void(disk_cache::BackendResult)> callback) {
DCHECK_GE(max_bytes_, 0);
disk_cache::ResetHandling reset_handling =
hard_reset_ ? disk_cache::ResetHandling::kReset
: disk_cache::ResetHandling::kResetOnError;
LOCAL_HISTOGRAM_BOOLEAN("HttpCache.HardReset", hard_reset_);
#if BUILDFLAG(IS_ANDROID)
if (app_status_listener_getter_) {
return disk_cache::CreateCacheBackend(
type_, backend_type_, file_operations_factory_, path_, max_bytes_,
reset_handling, net_log, std::move(callback),
app_status_listener_getter_);
}
#endif
return disk_cache::CreateCacheBackend(
type_, backend_type_, file_operations_factory_, path_, max_bytes_,
reset_handling, net_log, std::move(callback));
}
#if BUILDFLAG(IS_ANDROID)
void HttpCache::DefaultBackend::SetAppStatusListenerGetter(
disk_cache::ApplicationStatusListenerGetter app_status_listener_getter) {
app_status_listener_getter_ = std::move(app_status_listener_getter);
}
#endif
std::optional<CacheType> HttpCache::BackendFactory::GetCacheType() const {
return std::nullopt;
}
std::optional<CacheType> HttpCache::DefaultBackend::GetCacheType() const {
return type_;
}
HttpCache::ActiveEntry::ActiveEntry(base::WeakPtr<HttpCache> cache,
disk_cache::Entry* entry,
bool opened_in)
: cache_(std::move(cache)), disk_entry_(entry), opened_(opened_in) {
CHECK(disk_entry_);
cache_->active_entries_.emplace(disk_entry_->GetKey(),
base::raw_ref<ActiveEntry>::from_ptr(this));
}
HttpCache::ActiveEntry::~ActiveEntry() {
if (cache_) {
if (doomed_) {
FinalizeDoomed();
} else {
Deactivate();
}
}
}
void HttpCache::ActiveEntry::FinalizeDoomed() {
CHECK(doomed_);
auto it =
cache_->doomed_entries_.find(base::raw_ref<ActiveEntry>::from_ptr(this));
CHECK(it != cache_->doomed_entries_.end());
cache_->doomed_entries_.erase(it);
}
void HttpCache::ActiveEntry::Deactivate() {
CHECK(!doomed_);
std::string key = disk_entry_->GetKey();
if (key.empty()) {
SlowDeactivate();
return;
}
auto it = cache_->active_entries_.find(key);
CHECK(it != cache_->active_entries_.end());
CHECK(&it->second.get() == this);
cache_->active_entries_.erase(it);
}
void HttpCache::ActiveEntry::SlowDeactivate() {
CHECK(cache_);
for (auto it = cache_->active_entries_.begin();
it != cache_->active_entries_.end(); ++it) {
if (&it->second.get() == this) {
cache_->active_entries_.erase(it);
return;
}
}
}
bool HttpCache::ActiveEntry::TransactionInReaders(
Transaction* transaction) const {
return readers_.count(transaction) > 0;
}
void HttpCache::ActiveEntry::ReleaseWriters() {
writers_.reset();
}
void HttpCache::ActiveEntry::AddTransactionToWriters(
Transaction* transaction,
ParallelWritingPattern parallel_writing_pattern) {
CHECK(cache_);
if (!writers_) {
writers_ =
std::make_unique<Writers>(cache_.get(), base::WrapRefCounted(this));
} else {
ParallelWritingPattern writers_pattern;
DCHECK(writers_->CanAddWriters(&writers_pattern));
DCHECK_EQ(PARALLEL_WRITING_JOIN, writers_pattern);
}
Writers::TransactionInfo info(transaction->partial(),
transaction->is_truncated(),
*(transaction->GetResponseInfo()));
writers_->AddTransaction(transaction, parallel_writing_pattern,
transaction->priority(), info);
}
void HttpCache::ActiveEntry::Doom() {
doomed_ = true;
disk_entry_->Doom();
}
void HttpCache::ActiveEntry::RestartHeadersPhaseTransactions() {
if (headers_transaction_) {
RestartHeadersTransaction();
}
auto it = done_headers_queue_.begin();
while (it != done_headers_queue_.end()) {
Transaction* done_headers_transaction = *it;
it = done_headers_queue_.erase(it);
done_headers_transaction->cache_io_callback().Run(ERR_CACHE_RACE);
}
}
void HttpCache::ActiveEntry::RestartHeadersTransaction() {
Transaction* headers_transaction = headers_transaction_;
headers_transaction_ = nullptr;
headers_transaction->SetValidatingCannotProceed();
}
void HttpCache::ActiveEntry::ProcessAddToEntryQueue() {
DCHECK(!add_to_entry_queue_.empty());
if (headers_transaction_) {
return;
}
Transaction* transaction = add_to_entry_queue_.front();
add_to_entry_queue_.erase(add_to_entry_queue_.begin());
headers_transaction_ = transaction;
transaction->cache_io_callback().Run(OK);
}
bool HttpCache::ActiveEntry::RemovePendingTransaction(
Transaction* transaction) {
auto j =
find(add_to_entry_queue_.begin(), add_to_entry_queue_.end(), transaction);
if (j == add_to_entry_queue_.end()) {
return false;
}
add_to_entry_queue_.erase(j);
return true;
}
HttpCache::TransactionList HttpCache::ActiveEntry::TakeAllQueuedTransactions() {
TransactionList list = std::move(done_headers_queue_);
done_headers_queue_.clear();
list.splice(list.end(), add_to_entry_queue_);
add_to_entry_queue_.clear();
return list;
}
bool HttpCache::ActiveEntry::CanTransactionWriteResponseHeaders(
Transaction* transaction,
bool is_partial,
bool is_match) const {
if (writers_ && writers_->HasTransaction(transaction)) {
CHECK(is_partial);
return true;
}
if (transaction != headers_transaction_) {
return false;
}
if (!(transaction->mode() & Transaction::WRITE)) {
return false;
}
if (!is_match) {
return (!writers_ || writers_->IsEmpty()) && done_headers_queue_.empty() &&
readers_.empty();
}
return true;
}
const scoped_refptr<base::SingleThreadTaskRunner>&
HttpCache::ActiveEntry::GetTaskRunner() const {
RequestPriority highest = std::max(
{RequestPriority::IDLE, GetHighestPriority(done_headers_queue_),
GetHighestPriority(add_to_entry_queue_), GetHighestPriority(readers_)});
if (headers_transaction_) {
highest = std::max(highest, headers_transaction_->priority());
}
if (writers_) {
highest = std::max(highest, writers_->priority());
}
return TaskRunner(highest);
}
struct HttpCache::PendingOp {
PendingOp() = default;
~PendingOp() = default;
raw_ptr<disk_cache::Entry> entry = nullptr;
bool entry_opened = false;
std::unique_ptr<disk_cache::Backend> backend;
std::unique_ptr<WorkItem> writer;
bool callback_will_delete = false;
WorkItemList pending_queue;
};
class HttpCache::WorkItem {
public:
WorkItem(WorkItemOperation operation,
Transaction* transaction,
scoped_refptr<ActiveEntry>* entry)
: operation_(operation), transaction_(transaction), entry_(entry) {}
WorkItem(WorkItemOperation operation,
Transaction* transaction,
CompletionOnceCallback callback)
: operation_(operation),
transaction_(transaction),
entry_(nullptr),
callback_(std::move(callback)) {}
~WorkItem() = default;
void NotifyTransaction(int result, scoped_refptr<ActiveEntry> entry) {
if (entry_) {
*entry_ = std::move(entry);
}
if (transaction_) {
transaction_->cache_io_callback().Run(result);
}
}
bool DoCallback(int result) {
if (!callback_.is_null()) {
std::move(callback_).Run(result);
return true;
}
return false;
}
WorkItemOperation operation() { return operation_; }
void ClearTransaction() { transaction_ = nullptr; }
void ClearEntry() { entry_ = nullptr; }
void ClearCallback() { callback_.Reset(); }
bool Matches(Transaction* transaction) const {
return transaction == transaction_;
}
bool IsValid() const {
return transaction_ || entry_ || !callback_.is_null();
}
private:
WorkItemOperation operation_;
raw_ptr<Transaction, DanglingUntriaged> transaction_;
raw_ptr<scoped_refptr<ActiveEntry>, DanglingUntriaged> entry_;
CompletionOnceCallback callback_;
};
HttpCache::HttpCache(
std::unique_ptr<HttpTransactionFactory> network_layer,
std::unique_ptr<BackendFactory> backend_factory,
std::unique_ptr<NoVarySearchCacheStorageFileOperations> file_operations)
: net_log_(nullptr),
backend_factory_(std::move(backend_factory)),
network_layer_(std::move(network_layer)),
clock_(base::DefaultClock::GetInstance()),
keys_marked_no_store_(
features::kAvoidEntryCreationForNoStoreCacheSize.Get()),
file_operations_(std::move(file_operations)) {
g_init_cache = true;
if (base::FeatureList::IsEnabled(features::kHttpCacheNoVarySearch)) {
size_t max_entries = features::kHttpCacheNoVarySearchCacheMaxEntries.Get();
if (max_entries) {
no_vary_search_cache_ = std::make_unique<NoVarySearchCache>(max_entries);
}
}
HttpNetworkSession* session = network_layer_->GetSession();
if (!session) {
return;
}
net_log_ = session->net_log();
if (base::FeatureList::IsEnabled(kHttpCacheInitializeDiskCacheBackendEarly) &&
backend_factory_) {
if (auto maybe_cache_type = backend_factory_->GetCacheType()) {
if (*maybe_cache_type == CacheType::DISK_CACHE) {
CreateBackend(CompletionOnceCallback());
}
}
}
}
HttpCache::~HttpCache() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
weak_factory_.InvalidateWeakPtrs();
active_entries_.clear();
doomed_entries_.clear();
disk_cache_.reset();
for (auto& pending_it : pending_ops_) {
PendingOp* pending_op = pending_it.second;
pending_op->writer.reset();
bool delete_pending_op = true;
if (building_backend_ && pending_op->callback_will_delete) {
delete_pending_op = false;
}
pending_op->pending_queue.clear();
if (delete_pending_op) {
delete pending_op;
}
}
}
HttpCache::GetBackendResult HttpCache::GetBackend(GetBackendCallback callback) {
DCHECK(!callback.is_null());
if (disk_cache_.get()) {
return {OK, disk_cache_.get()};
}
int rv = CreateBackend(base::BindOnce(&HttpCache::ReportGetBackendResult,
GetWeakPtr(), std::move(callback)));
if (rv != ERR_IO_PENDING) {
return {rv, disk_cache_.get()};
}
return {ERR_IO_PENDING, nullptr};
}
void HttpCache::ReportGetBackendResult(GetBackendCallback callback,
int net_error) {
std::move(callback).Run(std::pair(net_error, disk_cache_.get()));
}
disk_cache::Backend* HttpCache::GetCurrentBackend() const {
return disk_cache_.get();
}
bool HttpCache::ParseResponseInfo(base::span<const uint8_t> data,
HttpResponseInfo* response_info,
bool* response_truncated) {
base::Pickle pickle = base::Pickle::WithUnownedBuffer(data);
return response_info->InitFromPickle(pickle, response_truncated);
}
void HttpCache::CloseAllConnections(int net_error,
const char* net_log_reason_utf8) {
HttpNetworkSession* session = GetSession();
if (session) {
session->CloseAllConnections(net_error, net_log_reason_utf8);
}
}
void HttpCache::CloseIdleConnections(const char* net_log_reason_utf8) {
HttpNetworkSession* session = GetSession();
if (session) {
session->CloseIdleConnections(net_log_reason_utf8);
}
}
void HttpCache::OnExternalCacheHit(
const GURL& url,
const std::string& http_method,
const NetworkIsolationKey& network_isolation_key,
bool used_credentials) {
if (!disk_cache_.get() || mode_ == DISABLE) {
return;
}
TRACE_EVENT("net", "HttpCache::OnExternalCacheHit");
HttpRequestInfo request_info;
request_info.url = url;
request_info.method = http_method;
request_info.network_isolation_key = network_isolation_key;
request_info.network_anonymization_key =
NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
network_isolation_key);
request_info.is_subframe_document_resource = false;
request_info.is_main_frame_navigation = false;
request_info.initiator = std::nullopt;
if (base::FeatureList::IsEnabled(features::kSplitCacheByIncludeCredentials)) {
if (!used_credentials) {
request_info.load_flags &= LOAD_DO_NOT_SAVE_COOKIES;
} else {
request_info.load_flags |= ~LOAD_DO_NOT_SAVE_COOKIES;
}
}
OnExternalCacheHitForRequest(request_info);
if (no_vary_search_cache_) {
auto result = no_vary_search_cache_->Lookup(request_info);
if (result) {
request_info.url = result->original_url;
OnExternalCacheHitForRequest(request_info);
}
}
}
void HttpCache::OnExternalCacheHitForRequest(
const HttpRequestInfo& request_info) {
std::optional<std::string> key = GenerateCacheKeyForRequest(&request_info);
if (!key) {
return;
}
disk_cache_->OnExternalCacheHit(*key);
}
void HttpCache::ClearNoVarySearchCache(
UrlFilterType filter_type,
const base::flat_set<url::Origin>& origins,
const base::flat_set<std::string>& domains,
base::Time delete_begin,
base::Time delete_end) {
if (!no_vary_search_cache_) {
return;
}
const bool cleared = no_vary_search_cache_->ClearData(
filter_type, origins, domains, delete_begin, delete_end);
if (cleared) {
no_vary_search_cache_storage_.TakeSnapshot();
}
}
std::unique_ptr<HttpTransaction> HttpCache::CreateTransaction(
RequestPriority priority) {
if (!disk_cache_.get()) {
CreateBackend(CompletionOnceCallback());
}
auto new_transaction =
std::make_unique<HttpCache::Transaction>(priority, this);
if (bypass_lock_for_test_) {
new_transaction->BypassLockForTest();
}
if (bypass_lock_after_headers_for_test_) {
new_transaction->BypassLockAfterHeadersForTest();
}
if (fail_conditionalization_for_test_) {
new_transaction->FailConditionalizationForTest();
}
return new_transaction;
}
HttpCache* HttpCache::GetCache() {
return this;
}
HttpNetworkSession* HttpCache::GetSession() {
return network_layer_->GetSession();
}
std::unique_ptr<HttpTransactionFactory>
HttpCache::SetHttpNetworkTransactionFactoryForTesting(
std::unique_ptr<HttpTransactionFactory> new_network_layer) {
std::unique_ptr<HttpTransactionFactory> old_network_layer(
std::move(network_layer_));
network_layer_ = std::move(new_network_layer);
return old_network_layer;
}
std::string HttpCache::GetResourceURLFromHttpCacheKey(const std::string& key) {
std::string::size_type pos = 0;
pos = key.find('/', pos) + 1;
pos = key.find('/', pos) + 1;
if (pos == std::string::npos) {
return "";
}
if (pos == key.find(kDoubleKeyPrefix, pos)) {
pos = key.rfind(kDoubleKeySeparator);
DCHECK_NE(pos, std::string::npos);
pos += strlen(kDoubleKeySeparator);
DCHECK_LE(pos, key.size() - 1);
}
return key.substr(pos);
}
bool HttpCache::CanGenerateCacheKeyForRequest(const HttpRequestInfo& request) {
if (IsSplitCacheEnabled()) {
if (request.network_isolation_key.IsTransient()) {
return false;
}
}
return true;
}
std::string HttpCache::GenerateCacheKey(
const GURL& url,
int load_flags,
const NetworkIsolationKey& network_isolation_key,
int64_t upload_data_identifier,
bool is_subframe_document_resource,
bool is_mainframe_navigation,
bool is_shared_resource,
std::optional<url::Origin> initiator,
bool include_url) {
const char credential_key = (base::FeatureList::IsEnabled(
features::kSplitCacheByIncludeCredentials) &&
(load_flags & LOAD_DO_NOT_SAVE_COOKIES))
? '0'
: '1';
std::string isolation_key;
if (!is_shared_resource && IsSplitCacheEnabled()) {
CHECK(!network_isolation_key.IsTransient());
std::string_view subframe_document_resource_prefix;
if (is_subframe_document_resource) {
subframe_document_resource_prefix = kSubframeDocumentResourcePrefix;
}
std::string_view is_cross_site_main_frame_navigation_prefix;
if (initiator.has_value() && is_mainframe_navigation) {
const bool is_initiator_cross_site =
!net::SchemefulSite::IsSameSite(*initiator, url::Origin::Create(url));
if (is_initiator_cross_site) {
is_cross_site_main_frame_navigation_prefix =
kCrossSiteMainFrameNavigationPrefix;
}
}
isolation_key = base::StrCat(
{kDoubleKeyPrefix, subframe_document_resource_prefix,
is_cross_site_main_frame_navigation_prefix,
*network_isolation_key.ToCacheKeyString(), kDoubleKeySeparator});
if (!include_url) {
isolation_key.pop_back();
}
}
return base::StringPrintf(
"%c/%" PRId64 "/%s%s", credential_key, upload_data_identifier,
isolation_key.c_str(),
include_url ? HttpUtil::SpecForRequest(url).c_str() : "");
}
std::optional<std::string> HttpCache::GenerateCacheKeyForRequest(
const HttpRequestInfo* request) {
return GenerateCacheKeyInternal(*request, true);
}
std::optional<std::string> HttpCache::GenerateCacheKeyInternal(
const HttpRequestInfo& request,
bool include_url) {
if (!CanGenerateCacheKeyForRequest(request)) {
return std::nullopt;
}
const int64_t upload_data_identifier =
request.upload_data_stream ? request.upload_data_stream->identifier()
: int64_t{0};
return GenerateCacheKey(
request.url, request.load_flags, request.network_isolation_key,
upload_data_identifier, request.is_subframe_document_resource,
request.is_main_frame_navigation, request.is_shared_resource,
request.initiator, include_url);
}
std::optional<std::string> HttpCache::GenerateCachePartitionKeyForRequest(
const HttpRequestInfo& request) {
return GenerateCacheKeyInternal(request, false);
}
void HttpCache::SplitCacheFeatureEnableByDefault() {
CHECK(!g_enable_split_cache && !g_init_cache);
if (!base::FeatureList::GetInstance()->IsFeatureOverridden(
"SplitCacheByNetworkIsolationKey")) {
g_enable_split_cache = true;
}
}
bool HttpCache::IsSplitCacheEnabled() {
return base::FeatureList::IsEnabled(
features::kSplitCacheByNetworkIsolationKey) ||
g_enable_split_cache;
}
void HttpCache::ClearGlobalsForTesting() {
g_init_cache = false;
g_enable_split_cache = false;
}
Error HttpCache::CreateAndSetWorkItem(scoped_refptr<ActiveEntry>* entry,
Transaction* transaction,
WorkItemOperation operation,
PendingOp* pending_op) {
auto item = std::make_unique<WorkItem>(operation, transaction, entry);
if (pending_op->writer) {
pending_op->pending_queue.push_back(std::move(item));
return ERR_IO_PENDING;
}
DCHECK(pending_op->pending_queue.empty());
pending_op->writer = std::move(item);
return OK;
}
int HttpCache::CreateBackend(CompletionOnceCallback callback) {
DCHECK(!disk_cache_);
if (!backend_factory_.get()) {
return ERR_FAILED;
}
building_backend_ = true;
const bool callback_is_null = callback.is_null();
std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
WI_CREATE_BACKEND, nullptr, std::move(callback));
PendingOp* pending_op = GetPendingOp(std::string());
if (pending_op->writer) {
if (!callback_is_null) {
pending_op->pending_queue.push_back(std::move(item));
}
return ERR_IO_PENDING;
}
DCHECK(pending_op->pending_queue.empty());
pending_op->writer = std::move(item);
disk_cache::BackendResult result = backend_factory_->CreateBackend(
net_log_, base::BindOnce(&HttpCache::OnPendingBackendCreationOpComplete,
GetWeakPtr(), pending_op));
if (result.net_error == ERR_IO_PENDING) {
pending_op->callback_will_delete = true;
return result.net_error;
}
pending_op->writer->ClearCallback();
int rv = result.net_error;
OnPendingBackendCreationOpComplete(GetWeakPtr(), pending_op,
std::move(result));
return rv;
}
int HttpCache::GetBackendForTransaction(Transaction* transaction) {
if (disk_cache_.get()) {
return OK;
}
if (!building_backend_) {
return ERR_FAILED;
}
std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
WI_CREATE_BACKEND, transaction, CompletionOnceCallback());
PendingOp* pending_op = GetPendingOp(std::string());
DCHECK(pending_op->writer);
pending_op->pending_queue.push_back(std::move(item));
return ERR_IO_PENDING;
}
void HttpCache::DoomActiveEntry(const std::string& key) {
auto it = active_entries_.find(key);
if (it == active_entries_.end()) {
return;
}
int rv = DoomEntry(key, nullptr);
DCHECK_EQ(OK, rv);
}
int HttpCache::DoomEntry(const std::string& key, Transaction* transaction) {
auto it = active_entries_.find(key);
if (it == active_entries_.end()) {
DCHECK(transaction);
return AsyncDoomEntry(key, transaction);
}
raw_ref<ActiveEntry> entry_ref = std::move(it->second);
active_entries_.erase(it);
ActiveEntry& entry = entry_ref.get();
DCHECK_EQ(0u, doomed_entries_.count(entry_ref));
doomed_entries_.insert(std::move(entry_ref));
entry.Doom();
return OK;
}
int HttpCache::AsyncDoomEntry(const std::string& key,
Transaction* transaction) {
PendingOp* pending_op = GetPendingOp(key);
int rv =
CreateAndSetWorkItem(nullptr, transaction, WI_DOOM_ENTRY, pending_op);
if (rv != OK) {
return rv;
}
RequestPriority priority = transaction ? transaction->priority() : LOWEST;
rv = disk_cache_->DoomEntry(key, priority,
base::BindOnce(&HttpCache::OnPendingOpComplete,
GetWeakPtr(), pending_op));
if (rv == ERR_IO_PENDING) {
pending_op->callback_will_delete = true;
return rv;
}
pending_op->writer->ClearTransaction();
OnPendingOpComplete(GetWeakPtr(), pending_op, rv);
return rv;
}
void HttpCache::DoomMainEntryForUrl(
const GURL& url,
const NetworkIsolationKey& isolation_key,
bool is_subframe_document_resource,
bool is_main_frame_navigation,
const std::optional<url::Origin>& initiator) {
if (!disk_cache_) {
return;
}
HttpRequestInfo temp_info;
temp_info.url = url;
temp_info.method = "GET";
temp_info.network_isolation_key = isolation_key;
temp_info.network_anonymization_key =
NetworkAnonymizationKey::CreateFromNetworkIsolationKey(isolation_key);
temp_info.is_subframe_document_resource = is_subframe_document_resource;
temp_info.is_main_frame_navigation = is_main_frame_navigation;
temp_info.initiator = initiator;
std::optional<std::string> key = GenerateCacheKeyForRequest(&temp_info);
if (!key) {
return;
}
if (active_entries_.count(*key)) {
DoomEntry(*key, nullptr);
} else {
AsyncDoomEntry(*key, nullptr);
}
}
bool HttpCache::HasActiveEntry(const std::string& key) {
return active_entries_.find(key) != active_entries_.end();
}
scoped_refptr<HttpCache::ActiveEntry> HttpCache::GetActiveEntry(
const std::string& key) {
auto it = active_entries_.find(key);
return it != active_entries_.end() ? base::WrapRefCounted(&it->second.get())
: nullptr;
}
scoped_refptr<HttpCache::ActiveEntry> HttpCache::ActivateEntry(
disk_cache::Entry* disk_entry,
bool opened) {
DCHECK(!HasActiveEntry(disk_entry->GetKey()));
return base::MakeRefCounted<ActiveEntry>(weak_factory_.GetWeakPtr(),
disk_entry, opened);
}
HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) {
DCHECK(!HasActiveEntry(key));
auto it = pending_ops_.find(key);
if (it != pending_ops_.end()) {
return it->second;
}
PendingOp* operation = new PendingOp();
pending_ops_[key] = operation;
return operation;
}
void HttpCache::DeletePendingOp(PendingOp* pending_op) {
std::string key;
if (pending_op->entry) {
key = pending_op->entry->GetKey();
}
if (!key.empty()) {
auto it = pending_ops_.find(key);
CHECK(it != pending_ops_.end());
pending_ops_.erase(it);
} else {
for (auto it = pending_ops_.begin(); it != pending_ops_.end(); ++it) {
if (it->second == pending_op) {
pending_ops_.erase(it);
break;
}
}
}
DCHECK(pending_op->pending_queue.empty());
delete pending_op;
}
int HttpCache::OpenOrCreateEntry(const std::string& key,
scoped_refptr<ActiveEntry>* entry,
Transaction* transaction) {
DCHECK(!HasActiveEntry(key));
PendingOp* pending_op = GetPendingOp(key);
int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_OR_CREATE_ENTRY,
pending_op);
if (rv != OK) {
return rv;
}
disk_cache::EntryResult entry_result = disk_cache_->OpenOrCreateEntry(
key, transaction->priority(),
base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
pending_op));
rv = entry_result.net_error();
if (rv == ERR_IO_PENDING) {
pending_op->callback_will_delete = true;
return ERR_IO_PENDING;
}
pending_op->writer->ClearTransaction();
OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
std::move(entry_result));
return rv;
}
int HttpCache::OpenEntry(const std::string& key,
scoped_refptr<ActiveEntry>* entry,
Transaction* transaction) {
DCHECK(!HasActiveEntry(key));
PendingOp* pending_op = GetPendingOp(key);
int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_ENTRY, pending_op);
if (rv != OK) {
return rv;
}
disk_cache::EntryResult entry_result = disk_cache_->OpenEntry(
key, transaction->priority(),
base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
pending_op));
rv = entry_result.net_error();
if (rv == ERR_IO_PENDING) {
pending_op->callback_will_delete = true;
return ERR_IO_PENDING;
}
pending_op->writer->ClearTransaction();
OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
std::move(entry_result));
return rv;
}
int HttpCache::CreateEntry(const std::string& key,
scoped_refptr<ActiveEntry>* entry,
Transaction* transaction) {
if (HasActiveEntry(key)) {
return ERR_CACHE_RACE;
}
PendingOp* pending_op = GetPendingOp(key);
int rv =
CreateAndSetWorkItem(entry, transaction, WI_CREATE_ENTRY, pending_op);
if (rv != OK) {
return rv;
}
disk_cache::EntryResult entry_result = disk_cache_->CreateEntry(
key, transaction->priority(),
base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
pending_op));
rv = entry_result.net_error();
if (rv == ERR_IO_PENDING) {
pending_op->callback_will_delete = true;
return ERR_IO_PENDING;
}
pending_op->writer->ClearTransaction();
OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
std::move(entry_result));
return rv;
}
int HttpCache::AddTransactionToEntry(scoped_refptr<ActiveEntry>& entry,
Transaction* transaction) {
DCHECK(entry);
DCHECK(entry->GetEntry());
entry->add_to_entry_queue().push_back(transaction);
if (!bypass_lock_for_test_) {
ProcessQueuedTransactions(entry);
}
return ERR_IO_PENDING;
}
int HttpCache::DoneWithResponseHeaders(scoped_refptr<ActiveEntry>& entry,
Transaction* transaction,
bool is_partial) {
if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
DCHECK(is_partial && entry->writers()->GetTransactionsCount() == 1);
return OK;
}
DCHECK_EQ(entry->headers_transaction(), transaction);
entry->ClearHeadersTransaction();
if ((transaction->mode() & Transaction::WRITE) && !entry->HasWriters() &&
entry->readers().empty()) {
entry->AddTransactionToWriters(
transaction, CanTransactionJoinExistingWriters(transaction));
ProcessQueuedTransactions(entry);
return OK;
}
entry->done_headers_queue().push_back(transaction);
ProcessQueuedTransactions(entry);
return ERR_IO_PENDING;
}
void HttpCache::DoneWithEntry(scoped_refptr<ActiveEntry>& entry,
Transaction* transaction,
bool entry_is_complete,
bool is_partial) {
bool is_mode_read_only = transaction->mode() == Transaction::READ;
if (!entry_is_complete && !is_mode_read_only && is_partial) {
entry->GetEntry()->CancelSparseIO();
}
auto it = std::ranges::find(entry->done_headers_queue(), transaction);
if (it != entry->done_headers_queue().end()) {
entry->done_headers_queue().erase(it);
if (!entry_is_complete && !is_mode_read_only) {
ProcessEntryFailure(entry.get());
}
return;
}
if (transaction == entry->headers_transaction()) {
entry->ClearHeadersTransaction();
if (entry_is_complete || is_mode_read_only) {
ProcessQueuedTransactions(entry);
} else {
ProcessEntryFailure(entry.get());
}
return;
}
if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
entry->writers()->RemoveTransaction(transaction,
entry_is_complete );
return;
}
DCHECK(!entry->HasWriters());
auto readers_it = entry->readers().find(transaction);
CHECK(readers_it != entry->readers().end());
entry->readers().erase(readers_it);
ProcessQueuedTransactions(entry);
}
void HttpCache::WritersDoomEntryRestartTransactions(ActiveEntry* entry) {
DCHECK(!entry->writers()->IsEmpty());
ProcessEntryFailure(entry);
}
void HttpCache::WritersDoneWritingToEntry(scoped_refptr<ActiveEntry> entry,
bool success,
bool should_keep_entry,
TransactionSet make_readers) {
DCHECK(entry->HasWriters());
DCHECK(entry->writers()->IsEmpty());
DCHECK(success || make_readers.empty());
if (!success && should_keep_entry) {
entry->RestartHeadersPhaseTransactions();
entry->ReleaseWriters();
return;
}
if (success) {
for (Transaction* reader : make_readers) {
reader->WriteModeTransactionAboutToBecomeReader();
entry->readers().insert(reader);
}
entry->ReleaseWriters();
ProcessQueuedTransactions(std::move(entry));
} else {
entry->ReleaseWriters();
ProcessEntryFailure(entry.get());
}
}
void HttpCache::DoomEntryValidationNoMatch(scoped_refptr<ActiveEntry> entry) {
DCHECK(entry->headers_transaction());
entry->ClearHeadersTransaction();
DoomActiveEntry(entry->GetEntry()->GetKey());
for (HttpCache::Transaction* transaction : entry->add_to_entry_queue()) {
transaction->ResetCachePendingState();
TaskRunner(transaction->priority())
->PostTask(FROM_HERE, base::BindOnce(transaction->cache_io_callback(),
ERR_CACHE_RACE));
}
entry->add_to_entry_queue().clear();
}
void HttpCache::ProcessEntryFailure(ActiveEntry* entry) {
if (entry->headers_transaction()) {
entry->RestartHeadersTransaction();
}
TransactionList list = entry->TakeAllQueuedTransactions();
DoomActiveEntry(entry->GetEntry()->GetKey());
for (Transaction* queued_transaction : list) {
queued_transaction->cache_io_callback().Run(ERR_CACHE_RACE);
}
}
void HttpCache::ProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
if (entry->will_process_queued_transactions()) {
return;
}
entry->set_will_process_queued_transactions(true);
entry->GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&HttpCache::OnProcessQueuedTransactions,
GetWeakPtr(), std::move(entry)));
}
void HttpCache::ProcessAddToEntryQueue(scoped_refptr<ActiveEntry> entry) {
CHECK(!entry->add_to_entry_queue().empty());
if (delay_add_transaction_to_entry_for_test_) {
entry->GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&HttpCache::ProcessAddToEntryQueueImpl,
GetWeakPtr(), std::move(entry)));
} else {
entry->ProcessAddToEntryQueue();
}
}
void HttpCache::ProcessAddToEntryQueueImpl(scoped_refptr<ActiveEntry> entry) {
entry->ProcessAddToEntryQueue();
}
HttpCache::ParallelWritingPattern HttpCache::CanTransactionJoinExistingWriters(
Transaction* transaction) {
if (transaction->method() != "GET") {
return PARALLEL_WRITING_NOT_JOIN_METHOD_NOT_GET;
}
if (transaction->partial()) {
return PARALLEL_WRITING_NOT_JOIN_RANGE;
}
if (transaction->mode() == Transaction::READ) {
return PARALLEL_WRITING_NOT_JOIN_READ_ONLY;
}
if (transaction->GetResponseInfo()->headers) {
std::optional<base::ByteCount> content_length =
transaction->GetResponseInfo()->headers->GetContentLength();
if (content_length &&
content_length->InBytes() > disk_cache_->MaxFileSize()) {
return PARALLEL_WRITING_NOT_JOIN_TOO_BIG_FOR_CACHE;
}
}
return PARALLEL_WRITING_JOIN;
}
void HttpCache::ProcessDoneHeadersQueue(scoped_refptr<ActiveEntry> entry) {
ParallelWritingPattern writers_pattern;
DCHECK(!entry->HasWriters() ||
entry->writers()->CanAddWriters(&writers_pattern));
DCHECK(!entry->done_headers_queue().empty());
Transaction* transaction = entry->done_headers_queue().front();
ParallelWritingPattern parallel_writing_pattern =
CanTransactionJoinExistingWriters(transaction);
if (entry->IsWritingInProgress()) {
if (parallel_writing_pattern != PARALLEL_WRITING_JOIN) {
return;
}
entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
} else {
if (transaction->mode() & Transaction::WRITE) {
if (transaction->partial()) {
if (entry->readers().empty()) {
entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
} else {
return;
}
} else {
transaction->WriteModeTransactionAboutToBecomeReader();
auto return_val = entry->readers().insert(transaction);
DCHECK(return_val.second);
}
} else {
auto return_val = entry->readers().insert(transaction);
DCHECK(return_val.second);
}
}
ProcessQueuedTransactions(entry);
entry->done_headers_queue().erase(entry->done_headers_queue().begin());
transaction->cache_io_callback().Run(OK);
}
LoadState HttpCache::GetLoadStateForPendingTransaction(
const Transaction* transaction) {
auto i = active_entries_.find(transaction->key());
if (i == active_entries_.end()) {
return LOAD_STATE_WAITING_FOR_CACHE;
}
Writers* writers = i->second->writers();
return !writers ? LOAD_STATE_WAITING_FOR_CACHE : writers->GetLoadState();
}
void HttpCache::RemovePendingTransaction(Transaction* transaction) {
auto i = active_entries_.find(transaction->key());
bool found = false;
if (i != active_entries_.end()) {
found = i->second->RemovePendingTransaction(transaction);
}
if (found) {
return;
}
if (building_backend_) {
auto j = pending_ops_.find(std::string());
if (j != pending_ops_.end()) {
found = RemovePendingTransactionFromPendingOp(j->second, transaction);
}
if (found) {
return;
}
}
auto j = pending_ops_.find(transaction->key());
if (j != pending_ops_.end()) {
found = RemovePendingTransactionFromPendingOp(j->second, transaction);
}
if (found) {
return;
}
for (auto k = doomed_entries_.begin(); k != doomed_entries_.end() && !found;
++k) {
found = k->get().RemovePendingTransaction(transaction);
}
DCHECK(found) << "Pending transaction not found";
}
bool HttpCache::RemovePendingTransactionFromPendingOp(
PendingOp* pending_op,
Transaction* transaction) {
if (pending_op->writer->Matches(transaction)) {
pending_op->writer->ClearTransaction();
pending_op->writer->ClearEntry();
return true;
}
WorkItemList& pending_queue = pending_op->pending_queue;
for (auto it = pending_queue.begin(); it != pending_queue.end(); ++it) {
if ((*it)->Matches(transaction)) {
pending_queue.erase(it);
return true;
}
}
return false;
}
void HttpCache::MarkKeyNoStore(const std::string& key) {
keys_marked_no_store_.Put(crypto::hash::Sha256(key));
}
bool HttpCache::DidKeyLeadToNoStoreResponse(const std::string& key) {
return keys_marked_no_store_.Get(crypto::hash::Sha256(key)) !=
keys_marked_no_store_.end();
}
void HttpCache::MaybeLoadNoVarySearchCacheFromDisk() {
if (file_operations_ && no_vary_search_cache_) {
no_vary_search_cache_storage_.Load(
std::move(file_operations_), no_vary_search_cache_->max_size(),
base::BindOnce(&HttpCache::OnNoVarySearchCacheLoadComplete,
base::Unretained(this)));
}
}
void HttpCache::OnProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
entry->set_will_process_queued_transactions(false);
if (entry->done_headers_queue().empty() &&
entry->add_to_entry_queue().empty()) {
return;
}
if (!entry->done_headers_queue().empty()) {
ParallelWritingPattern unused_reason;
if (!entry->writers() || entry->writers()->CanAddWriters(&unused_reason)) {
ProcessDoneHeadersQueue(entry);
return;
}
}
if (!entry->add_to_entry_queue().empty()) {
ProcessAddToEntryQueue(std::move(entry));
}
}
void HttpCache::OnIOComplete(int result, PendingOp* pending_op) {
WorkItemOperation op = pending_op->writer->operation();
if (op == WI_CREATE_BACKEND) {
return OnBackendCreated(result, pending_op);
}
std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
bool try_restart_requests = false;
scoped_refptr<ActiveEntry> entry;
std::string key;
if (result == OK) {
if (op == WI_DOOM_ENTRY) {
try_restart_requests = true;
} else if (item->IsValid()) {
DCHECK(pending_op->entry);
key = pending_op->entry->GetKey();
entry = ActivateEntry(pending_op->entry, pending_op->entry_opened);
} else {
if (!pending_op->entry_opened) {
pending_op->entry->Doom();
}
pending_op->entry.ExtractAsDangling()->Close();
pending_op->entry = nullptr;
try_restart_requests = true;
}
}
WorkItemList pending_items = std::move(pending_op->pending_queue);
DeletePendingOp(pending_op);
item->NotifyTransaction(result, entry);
while (!pending_items.empty()) {
item = std::move(pending_items.front());
pending_items.pop_front();
if (item->operation() == WI_DOOM_ENTRY) {
try_restart_requests = true;
} else if (result == OK) {
entry = GetActiveEntry(key);
if (!entry) {
try_restart_requests = true;
}
}
if (try_restart_requests) {
item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
continue;
}
if (item->operation() == WI_CREATE_ENTRY) {
if (result == OK) {
item->NotifyTransaction(ERR_CACHE_CREATE_FAILURE, nullptr);
} else {
if (op != WI_CREATE_ENTRY && op != WI_OPEN_OR_CREATE_ENTRY) {
item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
try_restart_requests = true;
} else {
item->NotifyTransaction(result, entry);
}
}
}
else if (item->operation() == WI_OPEN_OR_CREATE_ENTRY) {
if ((op == WI_OPEN_ENTRY || op == WI_CREATE_ENTRY) && result != OK) {
item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
try_restart_requests = true;
} else {
item->NotifyTransaction(result, entry);
}
}
else {
if (op == WI_CREATE_ENTRY && result != OK) {
item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
try_restart_requests = true;
} else {
item->NotifyTransaction(result, entry);
}
}
}
}
void HttpCache::OnPendingOpComplete(base::WeakPtr<HttpCache> cache,
PendingOp* pending_op,
int rv) {
if (cache.get()) {
pending_op->callback_will_delete = false;
cache->OnIOComplete(rv, pending_op);
} else {
delete pending_op;
}
}
void HttpCache::OnPendingCreationOpComplete(base::WeakPtr<HttpCache> cache,
PendingOp* pending_op,
disk_cache::EntryResult result) {
if (!cache.get()) {
delete pending_op;
return;
}
int rv = result.net_error();
pending_op->entry_opened = result.opened();
pending_op->entry = result.ReleaseEntry();
pending_op->callback_will_delete = false;
cache->OnIOComplete(rv, pending_op);
}
void HttpCache::OnPendingBackendCreationOpComplete(
base::WeakPtr<HttpCache> cache,
PendingOp* pending_op,
disk_cache::BackendResult result) {
if (!cache.get()) {
delete pending_op;
return;
}
int rv = result.net_error;
pending_op->backend = std::move(result.backend);
pending_op->callback_will_delete = false;
cache->OnIOComplete(rv, pending_op);
}
void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) {
std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
WorkItemOperation op = item->operation();
DCHECK_EQ(WI_CREATE_BACKEND, op);
if (backend_factory_.get()) {
backend_factory_.reset();
if (result == OK) {
disk_cache_ = std::move(pending_op->backend);
UMA_HISTOGRAM_MEMORY_KB("HttpCache.MaxFileSizeOnInit",
disk_cache_->MaxFileSize() / 1024);
MaybeLoadNoVarySearchCacheFromDisk();
}
}
if (!pending_op->pending_queue.empty()) {
std::unique_ptr<WorkItem> pending_item =
std::move(pending_op->pending_queue.front());
pending_op->pending_queue.pop_front();
DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation());
pending_op->writer = std::move(pending_item);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&HttpCache::OnBackendCreated, GetWeakPtr(),
result, pending_op));
} else {
building_backend_ = false;
DeletePendingOp(pending_op);
}
if (!item->DoCallback(result)) {
item->NotifyTransaction(result, nullptr);
}
}
void HttpCache::OnNoVarySearchCacheLoadComplete(
NoVarySearchCacheStorage::LoadResult result) {
if (!result.has_value()) {
return;
}
base::UmaHistogramCounts100(
"HttpCache.NoVarySearch.EntriesAddedDuringLoading",
no_vary_search_cache_->size());
auto provisional_no_vary_search_cache = std::move(no_vary_search_cache_);
no_vary_search_cache_ = std::move(result.value());
no_vary_search_cache_->MergeFrom(*provisional_no_vary_search_cache);
const size_t max_size = features::kHttpCacheNoVarySearchCacheMaxEntries.Get();
if (max_size >= 1) {
no_vary_search_cache_->SetMaxSize(max_size);
}
}
}