1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/341324165): Fix and remove.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/http/http_cache.h"
11
12 #include <optional>
13 #include <string_view>
14 #include <utility>
15
16 #include "base/compiler_specific.h"
17 #include "base/containers/span.h"
18 #include "base/feature_list.h"
19 #include "base/files/file_util.h"
20 #include "base/format_macros.h"
21 #include "base/functional/bind.h"
22 #include "base/functional/callback.h"
23 #include "base/functional/callback_helpers.h"
24 #include "base/hash/sha1.h"
25 #include "base/location.h"
26 #include "base/memory/ptr_util.h"
27 #include "base/memory/raw_ptr.h"
28 #include "base/memory/ref_counted.h"
29 #include "base/metrics/field_trial.h"
30 #include "base/metrics/histogram_macros.h"
31 #include "base/metrics/histogram_macros_local.h"
32 #include "base/not_fatal_until.h"
33 #include "base/numerics/safe_conversions.h"
34 #include "base/pickle.h"
35 #include "base/ranges/algorithm.h"
36 #include "base/strings/strcat.h"
37 #include "base/strings/string_number_conversions.h"
38 #include "base/strings/string_util.h"
39 #include "base/strings/stringprintf.h"
40 #include "base/task/single_thread_task_runner.h"
41 #include "base/time/default_clock.h"
42 #include "build/build_config.h"
43 #include "http_request_info.h"
44 #include "net/base/cache_type.h"
45 #include "net/base/features.h"
46 #include "net/base/io_buffer.h"
47 #include "net/base/load_flags.h"
48 #include "net/base/net_errors.h"
49 #include "net/base/network_anonymization_key.h"
50 #include "net/base/network_isolation_key.h"
51 #include "net/base/upload_data_stream.h"
52 #include "net/disk_cache/disk_cache.h"
53 #include "net/http/http_cache_transaction.h"
54 #include "net/http/http_cache_writers.h"
55 #include "net/http/http_network_layer.h"
56 #include "net/http/http_network_session.h"
57 #include "net/http/http_request_info.h"
58 #include "net/http/http_response_headers.h"
59 #include "net/http/http_response_info.h"
60 #include "net/http/http_util.h"
61 #include "net/log/net_log_with_source.h"
62 #include "net/quic/quic_server_info.h"
63 #include "url/origin.h"
64
65 #if BUILDFLAG(IS_POSIX)
66 #include <unistd.h>
67 #endif
68
69 namespace net {
70
71 namespace {
72 // True if any HTTP cache has been initialized.
73 bool g_init_cache = false;
74
75 // True if split cache is enabled by default. Must be set before any HTTP cache
76 // has been initialized.
77 bool g_enable_split_cache = false;
78
79 } // namespace
80
81 const char HttpCache::kDoubleKeyPrefix[] = "_dk_";
82 const char HttpCache::kDoubleKeySeparator[] = " ";
83 const char HttpCache::kSubframeDocumentResourcePrefix[] = "s_";
84
DefaultBackend(CacheType type,BackendType backend_type,scoped_refptr<disk_cache::BackendFileOperationsFactory> file_operations_factory,const base::FilePath & path,int max_bytes,bool hard_reset)85 HttpCache::DefaultBackend::DefaultBackend(
86 CacheType type,
87 BackendType backend_type,
88 scoped_refptr<disk_cache::BackendFileOperationsFactory>
89 file_operations_factory,
90 const base::FilePath& path,
91 int max_bytes,
92 bool hard_reset)
93 : type_(type),
94 backend_type_(backend_type),
95 file_operations_factory_(std::move(file_operations_factory)),
96 path_(path),
97 max_bytes_(max_bytes),
98 hard_reset_(hard_reset) {}
99
100 HttpCache::DefaultBackend::~DefaultBackend() = default;
101
102 // static
InMemory(int max_bytes)103 std::unique_ptr<HttpCache::BackendFactory> HttpCache::DefaultBackend::InMemory(
104 int max_bytes) {
105 return std::make_unique<DefaultBackend>(MEMORY_CACHE, CACHE_BACKEND_DEFAULT,
106 /*file_operations_factory=*/nullptr,
107 base::FilePath(), max_bytes, false);
108 }
109
CreateBackend(NetLog * net_log,base::OnceCallback<void (disk_cache::BackendResult)> callback)110 disk_cache::BackendResult HttpCache::DefaultBackend::CreateBackend(
111 NetLog* net_log,
112 base::OnceCallback<void(disk_cache::BackendResult)> callback) {
113 DCHECK_GE(max_bytes_, 0);
114 disk_cache::ResetHandling reset_handling =
115 hard_reset_ ? disk_cache::ResetHandling::kReset
116 : disk_cache::ResetHandling::kResetOnError;
117 LOCAL_HISTOGRAM_BOOLEAN("HttpCache.HardReset", hard_reset_);
118 #if BUILDFLAG(IS_ANDROID)
119 if (app_status_listener_getter_) {
120 return disk_cache::CreateCacheBackend(
121 type_, backend_type_, file_operations_factory_, path_, max_bytes_,
122 reset_handling, net_log, std::move(callback),
123 app_status_listener_getter_);
124 }
125 #endif
126 return disk_cache::CreateCacheBackend(
127 type_, backend_type_, file_operations_factory_, path_, max_bytes_,
128 reset_handling, net_log, std::move(callback));
129 }
130
131 #if BUILDFLAG(IS_ANDROID)
SetAppStatusListenerGetter(disk_cache::ApplicationStatusListenerGetter app_status_listener_getter)132 void HttpCache::DefaultBackend::SetAppStatusListenerGetter(
133 disk_cache::ApplicationStatusListenerGetter app_status_listener_getter) {
134 app_status_listener_getter_ = std::move(app_status_listener_getter);
135 }
136 #endif
137
138 //-----------------------------------------------------------------------------
139
ActiveEntry(base::WeakPtr<HttpCache> cache,disk_cache::Entry * entry,bool opened_in)140 HttpCache::ActiveEntry::ActiveEntry(base::WeakPtr<HttpCache> cache,
141 disk_cache::Entry* entry,
142 bool opened_in)
143 : cache_(std::move(cache)), disk_entry_(entry), opened_(opened_in) {
144 CHECK(disk_entry_);
145 cache_->active_entries_.emplace(disk_entry_->GetKey(),
146 base::raw_ref<ActiveEntry>::from_ptr(this));
147 }
148
~ActiveEntry()149 HttpCache::ActiveEntry::~ActiveEntry() {
150 if (cache_) {
151 if (doomed_) {
152 FinalizeDoomed();
153 } else {
154 Deactivate();
155 }
156 }
157 }
158
FinalizeDoomed()159 void HttpCache::ActiveEntry::FinalizeDoomed() {
160 CHECK(doomed_);
161
162 auto it =
163 cache_->doomed_entries_.find(base::raw_ref<ActiveEntry>::from_ptr(this));
164 CHECK(it != cache_->doomed_entries_.end());
165
166 cache_->doomed_entries_.erase(it);
167 }
168
Deactivate()169 void HttpCache::ActiveEntry::Deactivate() {
170 CHECK(!doomed_);
171
172 std::string key = disk_entry_->GetKey();
173 if (key.empty()) {
174 SlowDeactivate();
175 return;
176 }
177
178 auto it = cache_->active_entries_.find(key);
179 CHECK(it != cache_->active_entries_.end());
180 CHECK(&it->second.get() == this);
181
182 cache_->active_entries_.erase(it);
183 }
184
185 // TODO(ricea): Add unit test for this method.
SlowDeactivate()186 void HttpCache::ActiveEntry::SlowDeactivate() {
187 CHECK(cache_);
188 // We don't know this entry's key so we have to find it without it.
189 for (auto it = cache_->active_entries_.begin();
190 it != cache_->active_entries_.end(); ++it) {
191 if (&it->second.get() == this) {
192 cache_->active_entries_.erase(it);
193 return;
194 }
195 }
196 }
197
TransactionInReaders(Transaction * transaction) const198 bool HttpCache::ActiveEntry::TransactionInReaders(
199 Transaction* transaction) const {
200 return readers_.count(transaction) > 0;
201 }
202
ReleaseWriters()203 void HttpCache::ActiveEntry::ReleaseWriters() {
204 // May destroy `this`.
205 writers_.reset();
206 }
207
AddTransactionToWriters(Transaction * transaction,ParallelWritingPattern parallel_writing_pattern)208 void HttpCache::ActiveEntry::AddTransactionToWriters(
209 Transaction* transaction,
210 ParallelWritingPattern parallel_writing_pattern) {
211 CHECK(cache_);
212 if (!writers_) {
213 writers_ =
214 std::make_unique<Writers>(cache_.get(), base::WrapRefCounted(this));
215 } else {
216 ParallelWritingPattern writers_pattern;
217 DCHECK(writers_->CanAddWriters(&writers_pattern));
218 DCHECK_EQ(PARALLEL_WRITING_JOIN, writers_pattern);
219 }
220
221 Writers::TransactionInfo info(transaction->partial(),
222 transaction->is_truncated(),
223 *(transaction->GetResponseInfo()));
224
225 writers_->AddTransaction(transaction, parallel_writing_pattern,
226 transaction->priority(), info);
227 }
228
Doom()229 void HttpCache::ActiveEntry::Doom() {
230 doomed_ = true;
231 disk_entry_->Doom();
232 }
233
RestartHeadersPhaseTransactions()234 void HttpCache::ActiveEntry::RestartHeadersPhaseTransactions() {
235 if (headers_transaction_) {
236 RestartHeadersTransaction();
237 }
238
239 auto it = done_headers_queue_.begin();
240 while (it != done_headers_queue_.end()) {
241 Transaction* done_headers_transaction = *it;
242 it = done_headers_queue_.erase(it);
243 done_headers_transaction->cache_io_callback().Run(ERR_CACHE_RACE);
244 }
245 }
246
RestartHeadersTransaction()247 void HttpCache::ActiveEntry::RestartHeadersTransaction() {
248 Transaction* headers_transaction = headers_transaction_;
249 headers_transaction_ = nullptr;
250 // May destroy `this`.
251 headers_transaction->SetValidatingCannotProceed();
252 }
253
ProcessAddToEntryQueue()254 void HttpCache::ActiveEntry::ProcessAddToEntryQueue() {
255 DCHECK(!add_to_entry_queue_.empty());
256
257 // Note `this` may be new or may already have a response body written to it.
258 // In both cases, a transaction needs to wait since only one transaction can
259 // be in the headers phase at a time.
260 if (headers_transaction_) {
261 return;
262 }
263 Transaction* transaction = add_to_entry_queue_.front();
264 add_to_entry_queue_.erase(add_to_entry_queue_.begin());
265 headers_transaction_ = transaction;
266
267 transaction->cache_io_callback().Run(OK);
268 }
269
RemovePendingTransaction(Transaction * transaction)270 bool HttpCache::ActiveEntry::RemovePendingTransaction(
271 Transaction* transaction) {
272 auto j =
273 find(add_to_entry_queue_.begin(), add_to_entry_queue_.end(), transaction);
274 if (j == add_to_entry_queue_.end()) {
275 return false;
276 }
277
278 add_to_entry_queue_.erase(j);
279 return true;
280 }
281
TakeAllQueuedTransactions()282 HttpCache::TransactionList HttpCache::ActiveEntry::TakeAllQueuedTransactions() {
283 // Process done_headers_queue before add_to_entry_queue to maintain FIFO
284 // order.
285 TransactionList list = std::move(done_headers_queue_);
286 done_headers_queue_.clear();
287 list.splice(list.end(), add_to_entry_queue_);
288 add_to_entry_queue_.clear();
289 return list;
290 }
291
CanTransactionWriteResponseHeaders(Transaction * transaction,bool is_partial,bool is_match) const292 bool HttpCache::ActiveEntry::CanTransactionWriteResponseHeaders(
293 Transaction* transaction,
294 bool is_partial,
295 bool is_match) const {
296 // If |transaction| is the current writer, do nothing. This can happen for
297 // range requests since they can go back to headers phase after starting to
298 // write.
299 if (writers_ && writers_->HasTransaction(transaction)) {
300 CHECK(is_partial);
301 return true;
302 }
303
304 if (transaction != headers_transaction_) {
305 return false;
306 }
307
308 if (!(transaction->mode() & Transaction::WRITE)) {
309 return false;
310 }
311
312 // If its not a match then check if it is the transaction responsible for
313 // writing the response body.
314 if (!is_match) {
315 return (!writers_ || writers_->IsEmpty()) && done_headers_queue_.empty() &&
316 readers_.empty();
317 }
318
319 return true;
320 }
321
322 //-----------------------------------------------------------------------------
323
324 // This structure keeps track of work items that are attempting to create or
325 // open cache entries or the backend itself.
326 struct HttpCache::PendingOp {
327 PendingOp() = default;
328 ~PendingOp() = default;
329
330 raw_ptr<disk_cache::Entry, AcrossTasksDanglingUntriaged> entry = nullptr;
331 bool entry_opened = false; // rather than created.
332
333 std::unique_ptr<disk_cache::Backend> backend;
334 std::unique_ptr<WorkItem> writer;
335 // True if there is a posted OnPendingOpComplete() task that might delete
336 // |this| without removing it from |pending_ops_|. Note that since
337 // OnPendingOpComplete() is static, it will not get cancelled when HttpCache
338 // is destroyed.
339 bool callback_will_delete = false;
340 WorkItemList pending_queue;
341 };
342
343 //-----------------------------------------------------------------------------
344
345 // A work item encapsulates a single request to the backend with all the
346 // information needed to complete that request.
347 class HttpCache::WorkItem {
348 public:
WorkItem(WorkItemOperation operation,Transaction * transaction,scoped_refptr<ActiveEntry> * entry)349 WorkItem(WorkItemOperation operation,
350 Transaction* transaction,
351 scoped_refptr<ActiveEntry>* entry)
352 : operation_(operation), transaction_(transaction), entry_(entry) {}
WorkItem(WorkItemOperation operation,Transaction * transaction,CompletionOnceCallback callback)353 WorkItem(WorkItemOperation operation,
354 Transaction* transaction,
355 CompletionOnceCallback callback)
356 : operation_(operation),
357 transaction_(transaction),
358 entry_(nullptr),
359 callback_(std::move(callback)) {}
360 ~WorkItem() = default;
361
362 // Calls back the transaction with the result of the operation.
NotifyTransaction(int result,scoped_refptr<ActiveEntry> entry)363 void NotifyTransaction(int result, scoped_refptr<ActiveEntry> entry) {
364 if (entry_) {
365 *entry_ = std::move(entry);
366 }
367 if (transaction_) {
368 transaction_->cache_io_callback().Run(result);
369 }
370 }
371
372 // Notifies the caller about the operation completion. Returns true if the
373 // callback was invoked.
DoCallback(int result)374 bool DoCallback(int result) {
375 if (!callback_.is_null()) {
376 std::move(callback_).Run(result);
377 return true;
378 }
379 return false;
380 }
381
operation()382 WorkItemOperation operation() { return operation_; }
ClearTransaction()383 void ClearTransaction() { transaction_ = nullptr; }
ClearEntry()384 void ClearEntry() { entry_ = nullptr; }
ClearCallback()385 void ClearCallback() { callback_.Reset(); }
Matches(Transaction * transaction) const386 bool Matches(Transaction* transaction) const {
387 return transaction == transaction_;
388 }
IsValid() const389 bool IsValid() const {
390 return transaction_ || entry_ || !callback_.is_null();
391 }
392
393 private:
394 WorkItemOperation operation_;
395 raw_ptr<Transaction, DanglingUntriaged> transaction_;
396 raw_ptr<scoped_refptr<ActiveEntry>, DanglingUntriaged> entry_;
397 CompletionOnceCallback callback_; // User callback.
398 };
399
400 //-----------------------------------------------------------------------------
401
HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,std::unique_ptr<BackendFactory> backend_factory)402 HttpCache::HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,
403 std::unique_ptr<BackendFactory> backend_factory)
404 : net_log_(nullptr),
405 backend_factory_(std::move(backend_factory)),
406
407 network_layer_(std::move(network_layer)),
408 clock_(base::DefaultClock::GetInstance()),
409 keys_marked_no_store_(
410 features::kAvoidEntryCreationForNoStoreCacheSize.Get()) {
411 g_init_cache = true;
412 HttpNetworkSession* session = network_layer_->GetSession();
413 // Session may be NULL in unittests.
414 // TODO(mmenke): Seems like tests could be changed to provide a session,
415 // rather than having logic only used in unit tests here.
416 if (!session) {
417 return;
418 }
419
420 net_log_ = session->net_log();
421 }
422
~HttpCache()423 HttpCache::~HttpCache() {
424 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
425 // Transactions should see an invalid cache after this point; otherwise they
426 // could see an inconsistent object (half destroyed).
427 weak_factory_.InvalidateWeakPtrs();
428
429 active_entries_.clear();
430 doomed_entries_.clear();
431
432 // Before deleting pending_ops_, we have to make sure that the disk cache is
433 // done with said operations, or it will attempt to use deleted data.
434 disk_cache_.reset();
435
436 for (auto& pending_it : pending_ops_) {
437 // We are not notifying the transactions about the cache going away, even
438 // though they are waiting for a callback that will never fire.
439 PendingOp* pending_op = pending_it.second;
440 pending_op->writer.reset();
441 bool delete_pending_op = true;
442 if (building_backend_ && pending_op->callback_will_delete) {
443 // If we don't have a backend, when its construction finishes it will
444 // deliver the callbacks.
445 delete_pending_op = false;
446 }
447
448 pending_op->pending_queue.clear();
449 if (delete_pending_op) {
450 delete pending_op;
451 }
452 }
453 }
454
GetBackend(GetBackendCallback callback)455 HttpCache::GetBackendResult HttpCache::GetBackend(GetBackendCallback callback) {
456 DCHECK(!callback.is_null());
457
458 if (disk_cache_.get()) {
459 return {OK, disk_cache_.get()};
460 }
461
462 int rv = CreateBackend(base::BindOnce(&HttpCache::ReportGetBackendResult,
463 GetWeakPtr(), std::move(callback)));
464 if (rv != ERR_IO_PENDING) {
465 return {rv, disk_cache_.get()};
466 }
467 return {ERR_IO_PENDING, nullptr};
468 }
469
ReportGetBackendResult(GetBackendCallback callback,int net_error)470 void HttpCache::ReportGetBackendResult(GetBackendCallback callback,
471 int net_error) {
472 std::move(callback).Run(std::pair(net_error, disk_cache_.get()));
473 }
474
GetCurrentBackend() const475 disk_cache::Backend* HttpCache::GetCurrentBackend() const {
476 return disk_cache_.get();
477 }
478
479 // static
ParseResponseInfo(base::span<const uint8_t> data,HttpResponseInfo * response_info,bool * response_truncated)480 bool HttpCache::ParseResponseInfo(base::span<const uint8_t> data,
481 HttpResponseInfo* response_info,
482 bool* response_truncated) {
483 base::Pickle pickle = base::Pickle::WithUnownedBuffer(data);
484 return response_info->InitFromPickle(pickle, response_truncated);
485 }
486
CloseAllConnections(int net_error,const char * net_log_reason_utf8)487 void HttpCache::CloseAllConnections(int net_error,
488 const char* net_log_reason_utf8) {
489 HttpNetworkSession* session = GetSession();
490 if (session) {
491 session->CloseAllConnections(net_error, net_log_reason_utf8);
492 }
493 }
494
CloseIdleConnections(const char * net_log_reason_utf8)495 void HttpCache::CloseIdleConnections(const char* net_log_reason_utf8) {
496 HttpNetworkSession* session = GetSession();
497 if (session) {
498 session->CloseIdleConnections(net_log_reason_utf8);
499 }
500 }
501
OnExternalCacheHit(const GURL & url,const std::string & http_method,const NetworkIsolationKey & network_isolation_key,bool used_credentials)502 void HttpCache::OnExternalCacheHit(
503 const GURL& url,
504 const std::string& http_method,
505 const NetworkIsolationKey& network_isolation_key,
506 bool used_credentials) {
507 if (!disk_cache_.get() || mode_ == DISABLE) {
508 return;
509 }
510
511 HttpRequestInfo request_info;
512 request_info.url = url;
513 request_info.method = http_method;
514 request_info.network_isolation_key = network_isolation_key;
515 request_info.network_anonymization_key =
516 NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
517 network_isolation_key);
518 // This method is only called for cache hits on subresources, so mark this
519 // request as not being a main frame or subframe navigation.
520 request_info.is_subframe_document_resource = false;
521 request_info.is_main_frame_navigation = false;
522 request_info.initiator = std::nullopt;
523 if (base::FeatureList::IsEnabled(features::kSplitCacheByIncludeCredentials)) {
524 if (!used_credentials) {
525 request_info.load_flags &= LOAD_DO_NOT_SAVE_COOKIES;
526 } else {
527 request_info.load_flags |= ~LOAD_DO_NOT_SAVE_COOKIES;
528 }
529 }
530
531 std::optional<std::string> key = GenerateCacheKeyForRequest(&request_info);
532 if (!key) {
533 return;
534 }
535 disk_cache_->OnExternalCacheHit(*key);
536 }
537
CreateTransaction(RequestPriority priority,std::unique_ptr<HttpTransaction> * transaction)538 int HttpCache::CreateTransaction(
539 RequestPriority priority,
540 std::unique_ptr<HttpTransaction>* transaction) {
541 // Do lazy initialization of disk cache if needed.
542 if (!disk_cache_.get()) {
543 // We don't care about the result.
544 CreateBackend(CompletionOnceCallback());
545 }
546
547 auto new_transaction =
548 std::make_unique<HttpCache::Transaction>(priority, this);
549 if (bypass_lock_for_test_) {
550 new_transaction->BypassLockForTest();
551 }
552 if (bypass_lock_after_headers_for_test_) {
553 new_transaction->BypassLockAfterHeadersForTest();
554 }
555 if (fail_conditionalization_for_test_) {
556 new_transaction->FailConditionalizationForTest();
557 }
558
559 *transaction = std::move(new_transaction);
560 return OK;
561 }
562
GetCache()563 HttpCache* HttpCache::GetCache() {
564 return this;
565 }
566
GetSession()567 HttpNetworkSession* HttpCache::GetSession() {
568 return network_layer_->GetSession();
569 }
570
571 std::unique_ptr<HttpTransactionFactory>
SetHttpNetworkTransactionFactoryForTesting(std::unique_ptr<HttpTransactionFactory> new_network_layer)572 HttpCache::SetHttpNetworkTransactionFactoryForTesting(
573 std::unique_ptr<HttpTransactionFactory> new_network_layer) {
574 std::unique_ptr<HttpTransactionFactory> old_network_layer(
575 std::move(network_layer_));
576 network_layer_ = std::move(new_network_layer);
577 return old_network_layer;
578 }
579
580 // static
GetResourceURLFromHttpCacheKey(const std::string & key)581 std::string HttpCache::GetResourceURLFromHttpCacheKey(const std::string& key) {
582 // The key format is:
583 // credential_key/post_key/[isolation_key]url
584
585 std::string::size_type pos = 0;
586 pos = key.find('/', pos) + 1; // Consume credential_key/
587 pos = key.find('/', pos) + 1; // Consume post_key/
588
589 // It is a good idea to make this function tolerate invalid input. This can
590 // happen because of disk corruption.
591 if (pos == std::string::npos) {
592 return "";
593 }
594
595 // Consume [isolation_key].
596 // Search the key to see whether it begins with |kDoubleKeyPrefix|. If so,
597 // then the entry was double-keyed.
598 if (pos == key.find(kDoubleKeyPrefix, pos)) {
599 // Find the rightmost occurrence of |kDoubleKeySeparator|, as when both
600 // the top-frame origin and the initiator are added to the key, there will
601 // be two occurrences of |kDoubleKeySeparator|. When the cache entry is
602 // originally written to disk, GenerateCacheKey method calls
603 // HttpUtil::SpecForRequest method, which has a DCHECK to ensure that
604 // the original resource url is valid, and hence will not contain the
605 // unescaped whitespace of |kDoubleKeySeparator|.
606 pos = key.rfind(kDoubleKeySeparator);
607 DCHECK_NE(pos, std::string::npos);
608 pos += strlen(kDoubleKeySeparator);
609 DCHECK_LE(pos, key.size() - 1);
610 }
611 return key.substr(pos);
612 }
613
614 // static
CanGenerateCacheKeyForRequest(const HttpRequestInfo * request)615 bool HttpCache::CanGenerateCacheKeyForRequest(const HttpRequestInfo* request) {
616 if (IsSplitCacheEnabled()) {
617 if (request->network_isolation_key.IsTransient()) {
618 return false;
619 }
620 // If the initiator is opaque, it would serialize to 'null' if used, which
621 // would mean that navigations initiated from all opaque origins would share
622 // a cache partition. To avoid this, we won't cache navigations where the
623 // initiator is an opaque origin if the initiator would be used as part of
624 // the cache key.
625 if (request->initiator.has_value() && request->initiator->opaque()) {
626 switch (HttpCache::GetExperimentMode()) {
627 case HttpCache::ExperimentMode::kStandard:
628 case HttpCache::ExperimentMode::kCrossSiteInitiatorBoolean:
629 break;
630 case HttpCache::ExperimentMode::kMainFrameNavigationInitiator:
631 if (request->is_main_frame_navigation) {
632 return false;
633 }
634 break;
635 case HttpCache::ExperimentMode::kNavigationInitiator:
636 if (request->is_main_frame_navigation ||
637 request->is_subframe_document_resource) {
638 return false;
639 }
640 break;
641 }
642 }
643 }
644 return true;
645 }
646
647 // static
648 // Generate a key that can be used inside the cache.
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,std::optional<url::Origin> initiator)649 std::string HttpCache::GenerateCacheKey(
650 const GURL& url,
651 int load_flags,
652 const NetworkIsolationKey& network_isolation_key,
653 int64_t upload_data_identifier,
654 bool is_subframe_document_resource,
655 bool is_mainframe_navigation,
656 std::optional<url::Origin> initiator) {
657 // The first character of the key may vary depending on whether or not sending
658 // credentials is permitted for this request. This only happens if the
659 // SplitCacheByIncludeCredentials feature is enabled.
660 const char credential_key = (base::FeatureList::IsEnabled(
661 features::kSplitCacheByIncludeCredentials) &&
662 (load_flags & LOAD_DO_NOT_SAVE_COOKIES))
663 ? '0'
664 : '1';
665
666 std::string isolation_key;
667 if (IsSplitCacheEnabled()) {
668 // Prepend the key with |kDoubleKeyPrefix| = "_dk_" to mark it as
669 // double-keyed (and makes it an invalid url so that it doesn't get
670 // confused with a single-keyed entry). Separate the origin and url
671 // with invalid whitespace character |kDoubleKeySeparator|.
672 CHECK(!network_isolation_key.IsTransient());
673
674 const ExperimentMode experiment_mode = HttpCache::GetExperimentMode();
675 std::string_view subframe_document_resource_prefix;
676 if (is_subframe_document_resource) {
677 switch (experiment_mode) {
678 case HttpCache::ExperimentMode::kStandard:
679 case HttpCache::ExperimentMode::kCrossSiteInitiatorBoolean:
680 case HttpCache::ExperimentMode::kMainFrameNavigationInitiator:
681 subframe_document_resource_prefix = kSubframeDocumentResourcePrefix;
682 break;
683 case HttpCache::ExperimentMode::kNavigationInitiator:
684 // No need to set `subframe_document_resource_prefix` if we are
685 // keying all cross-site navigations on initiator below.
686 break;
687 }
688 }
689
690 std::string navigation_experiment_prefix;
691 if (initiator.has_value() &&
692 (is_mainframe_navigation || is_subframe_document_resource)) {
693 const auto initiator_site = net::SchemefulSite(*initiator);
694 const bool is_initiator_cross_site =
695 initiator_site != net::SchemefulSite(url);
696
697 if (is_initiator_cross_site) {
698 switch (experiment_mode) {
699 case HttpCache::ExperimentMode::kStandard:
700 break;
701 case HttpCache::ExperimentMode::kCrossSiteInitiatorBoolean:
702 if (is_mainframe_navigation) {
703 navigation_experiment_prefix = "csnb_ ";
704 }
705 break;
706 case HttpCache::ExperimentMode::kMainFrameNavigationInitiator:
707 if (is_mainframe_navigation) {
708 CHECK(!initiator_site.opaque());
709 navigation_experiment_prefix =
710 base::StrCat({"mfni_", initiator_site.Serialize(), " "});
711 }
712 break;
713 case HttpCache::ExperimentMode::kNavigationInitiator:
714 if (is_mainframe_navigation || is_subframe_document_resource) {
715 CHECK(!initiator_site.opaque());
716 navigation_experiment_prefix =
717 base::StrCat({"ni_", initiator_site.Serialize(), " "});
718 }
719 break;
720 }
721 }
722 }
723 isolation_key = base::StrCat(
724 {kDoubleKeyPrefix, subframe_document_resource_prefix,
725 navigation_experiment_prefix,
726 *network_isolation_key.ToCacheKeyString(), kDoubleKeySeparator});
727 }
728
729 // The key format is:
730 // credential_key/upload_data_identifier/[isolation_key]url
731
732 // Strip out the reference, username, and password sections of the URL and
733 // concatenate with the credential_key, the post_key, and the network
734 // isolation key if we are splitting the cache.
735 return base::StringPrintf("%c/%" PRId64 "/%s%s", credential_key,
736 upload_data_identifier, isolation_key.c_str(),
737 HttpUtil::SpecForRequest(url).c_str());
738 }
739
740 // static
GetExperimentMode()741 HttpCache::ExperimentMode HttpCache::GetExperimentMode() {
742 bool cross_site_main_frame_navigation_boolean_enabled =
743 base::FeatureList::IsEnabled(
744 net::features::kSplitCacheByCrossSiteMainFrameNavigationBoolean);
745 bool main_frame_navigation_initiator_enabled = base::FeatureList::IsEnabled(
746 net::features::kSplitCacheByMainFrameNavigationInitiator);
747 bool navigation_initiator_enabled = base::FeatureList::IsEnabled(
748 net::features::kSplitCacheByNavigationInitiator);
749
750 if (cross_site_main_frame_navigation_boolean_enabled) {
751 if (main_frame_navigation_initiator_enabled ||
752 navigation_initiator_enabled) {
753 return ExperimentMode::kStandard;
754 }
755 return ExperimentMode::kCrossSiteInitiatorBoolean;
756 } else if (main_frame_navigation_initiator_enabled) {
757 if (navigation_initiator_enabled) {
758 return ExperimentMode::kStandard;
759 }
760 return ExperimentMode::kMainFrameNavigationInitiator;
761 } else if (navigation_initiator_enabled) {
762 return ExperimentMode::kNavigationInitiator;
763 }
764 return ExperimentMode::kStandard;
765 }
766
767 // static
GenerateCacheKeyForRequest(const HttpRequestInfo * request)768 std::optional<std::string> HttpCache::GenerateCacheKeyForRequest(
769 const HttpRequestInfo* request) {
770 CHECK(request);
771
772 if (!CanGenerateCacheKeyForRequest(request)) {
773 return std::nullopt;
774 }
775
776 const int64_t upload_data_identifier =
777 request->upload_data_stream ? request->upload_data_stream->identifier()
778 : int64_t(0);
779 return GenerateCacheKey(
780 request->url, request->load_flags, request->network_isolation_key,
781 upload_data_identifier, request->is_subframe_document_resource,
782 request->is_main_frame_navigation, request->initiator);
783 }
784
785 // static
SplitCacheFeatureEnableByDefault()786 void HttpCache::SplitCacheFeatureEnableByDefault() {
787 CHECK(!g_enable_split_cache && !g_init_cache);
788 if (!base::FeatureList::GetInstance()->IsFeatureOverridden(
789 "SplitCacheByNetworkIsolationKey")) {
790 g_enable_split_cache = true;
791 }
792 }
793
794 // static
IsSplitCacheEnabled()795 bool HttpCache::IsSplitCacheEnabled() {
796 return base::FeatureList::IsEnabled(
797 features::kSplitCacheByNetworkIsolationKey) ||
798 g_enable_split_cache;
799 }
800
801 // static
ClearGlobalsForTesting()802 void HttpCache::ClearGlobalsForTesting() {
803 // Reset these so that unit tests can work.
804 g_init_cache = false;
805 g_enable_split_cache = false;
806 }
807
808 //-----------------------------------------------------------------------------
809
CreateAndSetWorkItem(scoped_refptr<ActiveEntry> * entry,Transaction * transaction,WorkItemOperation operation,PendingOp * pending_op)810 Error HttpCache::CreateAndSetWorkItem(scoped_refptr<ActiveEntry>* entry,
811 Transaction* transaction,
812 WorkItemOperation operation,
813 PendingOp* pending_op) {
814 auto item = std::make_unique<WorkItem>(operation, transaction, entry);
815
816 if (pending_op->writer) {
817 pending_op->pending_queue.push_back(std::move(item));
818 return ERR_IO_PENDING;
819 }
820
821 DCHECK(pending_op->pending_queue.empty());
822
823 pending_op->writer = std::move(item);
824 return OK;
825 }
826
CreateBackend(CompletionOnceCallback callback)827 int HttpCache::CreateBackend(CompletionOnceCallback callback) {
828 DCHECK(!disk_cache_);
829
830 if (!backend_factory_.get()) {
831 return ERR_FAILED;
832 }
833
834 building_backend_ = true;
835
836 const bool callback_is_null = callback.is_null();
837 std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
838 WI_CREATE_BACKEND, nullptr, std::move(callback));
839
840 // This is the only operation that we can do that is not related to any given
841 // entry, so we use an empty key for it.
842 PendingOp* pending_op = GetPendingOp(std::string());
843 if (pending_op->writer) {
844 if (!callback_is_null) {
845 pending_op->pending_queue.push_back(std::move(item));
846 }
847 return ERR_IO_PENDING;
848 }
849
850 DCHECK(pending_op->pending_queue.empty());
851
852 pending_op->writer = std::move(item);
853
854 disk_cache::BackendResult result = backend_factory_->CreateBackend(
855 net_log_, base::BindOnce(&HttpCache::OnPendingBackendCreationOpComplete,
856 GetWeakPtr(), pending_op));
857 if (result.net_error == ERR_IO_PENDING) {
858 pending_op->callback_will_delete = true;
859 return result.net_error;
860 }
861
862 pending_op->writer->ClearCallback();
863 int rv = result.net_error;
864 OnPendingBackendCreationOpComplete(GetWeakPtr(), pending_op,
865 std::move(result));
866 return rv;
867 }
868
GetBackendForTransaction(Transaction * transaction)869 int HttpCache::GetBackendForTransaction(Transaction* transaction) {
870 if (disk_cache_.get()) {
871 return OK;
872 }
873
874 if (!building_backend_) {
875 return ERR_FAILED;
876 }
877
878 std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
879 WI_CREATE_BACKEND, transaction, CompletionOnceCallback());
880 PendingOp* pending_op = GetPendingOp(std::string());
881 DCHECK(pending_op->writer);
882 pending_op->pending_queue.push_back(std::move(item));
883 return ERR_IO_PENDING;
884 }
885
DoomActiveEntry(const std::string & key)886 void HttpCache::DoomActiveEntry(const std::string& key) {
887 auto it = active_entries_.find(key);
888 if (it == active_entries_.end()) {
889 return;
890 }
891
892 // This is not a performance critical operation, this is handling an error
893 // condition so it is OK to look up the entry again.
894 int rv = DoomEntry(key, nullptr);
895 DCHECK_EQ(OK, rv);
896 }
897
DoomEntry(const std::string & key,Transaction * transaction)898 int HttpCache::DoomEntry(const std::string& key, Transaction* transaction) {
899 // Need to abandon the ActiveEntry, but any transaction attached to the entry
900 // should not be impacted. Dooming an entry only means that it will no longer
901 // be returned by GetActiveEntry (and it will also be destroyed once all
902 // consumers are finished with the entry).
903 auto it = active_entries_.find(key);
904 if (it == active_entries_.end()) {
905 DCHECK(transaction);
906 return AsyncDoomEntry(key, transaction);
907 }
908
909 raw_ref<ActiveEntry> entry_ref = std::move(it->second);
910 active_entries_.erase(it);
911
912 // We keep track of doomed entries so that we can ensure that they are
913 // cleaned up properly when the cache is destroyed.
914 ActiveEntry& entry = entry_ref.get();
915 DCHECK_EQ(0u, doomed_entries_.count(entry_ref));
916 doomed_entries_.insert(std::move(entry_ref));
917
918 entry.Doom();
919
920 return OK;
921 }
922
AsyncDoomEntry(const std::string & key,Transaction * transaction)923 int HttpCache::AsyncDoomEntry(const std::string& key,
924 Transaction* transaction) {
925 PendingOp* pending_op = GetPendingOp(key);
926 int rv =
927 CreateAndSetWorkItem(nullptr, transaction, WI_DOOM_ENTRY, pending_op);
928 if (rv != OK) {
929 return rv;
930 }
931
932 RequestPriority priority = transaction ? transaction->priority() : LOWEST;
933 rv = disk_cache_->DoomEntry(key, priority,
934 base::BindOnce(&HttpCache::OnPendingOpComplete,
935 GetWeakPtr(), pending_op));
936 if (rv == ERR_IO_PENDING) {
937 pending_op->callback_will_delete = true;
938 return rv;
939 }
940
941 pending_op->writer->ClearTransaction();
942 OnPendingOpComplete(GetWeakPtr(), pending_op, rv);
943 return rv;
944 }
945
DoomMainEntryForUrl(const GURL & url,const NetworkIsolationKey & isolation_key,bool is_subframe_document_resource,bool is_main_frame_navigation,const std::optional<url::Origin> & initiator)946 void HttpCache::DoomMainEntryForUrl(
947 const GURL& url,
948 const NetworkIsolationKey& isolation_key,
949 bool is_subframe_document_resource,
950 bool is_main_frame_navigation,
951 const std::optional<url::Origin>& initiator) {
952 if (!disk_cache_) {
953 return;
954 }
955
956 HttpRequestInfo temp_info;
957 temp_info.url = url;
958 temp_info.method = "GET";
959 temp_info.network_isolation_key = isolation_key;
960 temp_info.network_anonymization_key =
961 NetworkAnonymizationKey::CreateFromNetworkIsolationKey(isolation_key);
962 temp_info.is_subframe_document_resource = is_subframe_document_resource;
963 temp_info.is_main_frame_navigation = is_main_frame_navigation;
964 temp_info.initiator = initiator;
965
966 std::optional<std::string> key = GenerateCacheKeyForRequest(&temp_info);
967 if (!key) {
968 return;
969 }
970
971 // Defer to DoomEntry if there is an active entry, otherwise call
972 // AsyncDoomEntry without triggering a callback.
973 if (active_entries_.count(*key)) {
974 DoomEntry(*key, nullptr);
975 } else {
976 AsyncDoomEntry(*key, nullptr);
977 }
978 }
979
HasActiveEntry(const std::string & key)980 bool HttpCache::HasActiveEntry(const std::string& key) {
981 return active_entries_.find(key) != active_entries_.end();
982 }
983
GetActiveEntry(const std::string & key)984 scoped_refptr<HttpCache::ActiveEntry> HttpCache::GetActiveEntry(
985 const std::string& key) {
986 auto it = active_entries_.find(key);
987 return it != active_entries_.end() ? base::WrapRefCounted(&it->second.get())
988 : nullptr;
989 }
990
ActivateEntry(disk_cache::Entry * disk_entry,bool opened)991 scoped_refptr<HttpCache::ActiveEntry> HttpCache::ActivateEntry(
992 disk_cache::Entry* disk_entry,
993 bool opened) {
994 DCHECK(!HasActiveEntry(disk_entry->GetKey()));
995 return base::MakeRefCounted<ActiveEntry>(weak_factory_.GetWeakPtr(),
996 disk_entry, opened);
997 }
998
GetPendingOp(const std::string & key)999 HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) {
1000 DCHECK(!HasActiveEntry(key));
1001
1002 auto it = pending_ops_.find(key);
1003 if (it != pending_ops_.end()) {
1004 return it->second;
1005 }
1006
1007 PendingOp* operation = new PendingOp();
1008 pending_ops_[key] = operation;
1009 return operation;
1010 }
1011
DeletePendingOp(PendingOp * pending_op)1012 void HttpCache::DeletePendingOp(PendingOp* pending_op) {
1013 std::string key;
1014 if (pending_op->entry) {
1015 key = pending_op->entry->GetKey();
1016 }
1017
1018 if (!key.empty()) {
1019 auto it = pending_ops_.find(key);
1020 CHECK(it != pending_ops_.end(), base::NotFatalUntil::M130);
1021 pending_ops_.erase(it);
1022 } else {
1023 for (auto it = pending_ops_.begin(); it != pending_ops_.end(); ++it) {
1024 if (it->second == pending_op) {
1025 pending_ops_.erase(it);
1026 break;
1027 }
1028 }
1029 }
1030 DCHECK(pending_op->pending_queue.empty());
1031
1032 delete pending_op;
1033 }
1034
OpenOrCreateEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)1035 int HttpCache::OpenOrCreateEntry(const std::string& key,
1036 scoped_refptr<ActiveEntry>* entry,
1037 Transaction* transaction) {
1038 DCHECK(!HasActiveEntry(key));
1039
1040 PendingOp* pending_op = GetPendingOp(key);
1041 int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_OR_CREATE_ENTRY,
1042 pending_op);
1043 if (rv != OK) {
1044 return rv;
1045 }
1046
1047 disk_cache::EntryResult entry_result = disk_cache_->OpenOrCreateEntry(
1048 key, transaction->priority(),
1049 base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
1050 pending_op));
1051 rv = entry_result.net_error();
1052 if (rv == ERR_IO_PENDING) {
1053 pending_op->callback_will_delete = true;
1054 return ERR_IO_PENDING;
1055 }
1056
1057 pending_op->writer->ClearTransaction();
1058 OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
1059 std::move(entry_result));
1060 return rv;
1061 }
1062
OpenEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)1063 int HttpCache::OpenEntry(const std::string& key,
1064 scoped_refptr<ActiveEntry>* entry,
1065 Transaction* transaction) {
1066 DCHECK(!HasActiveEntry(key));
1067
1068 PendingOp* pending_op = GetPendingOp(key);
1069 int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_ENTRY, pending_op);
1070 if (rv != OK) {
1071 return rv;
1072 }
1073
1074 disk_cache::EntryResult entry_result = disk_cache_->OpenEntry(
1075 key, transaction->priority(),
1076 base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
1077 pending_op));
1078 rv = entry_result.net_error();
1079 if (rv == ERR_IO_PENDING) {
1080 pending_op->callback_will_delete = true;
1081 return ERR_IO_PENDING;
1082 }
1083
1084 pending_op->writer->ClearTransaction();
1085 OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
1086 std::move(entry_result));
1087 return rv;
1088 }
1089
CreateEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)1090 int HttpCache::CreateEntry(const std::string& key,
1091 scoped_refptr<ActiveEntry>* entry,
1092 Transaction* transaction) {
1093 if (HasActiveEntry(key)) {
1094 return ERR_CACHE_RACE;
1095 }
1096
1097 PendingOp* pending_op = GetPendingOp(key);
1098 int rv =
1099 CreateAndSetWorkItem(entry, transaction, WI_CREATE_ENTRY, pending_op);
1100 if (rv != OK) {
1101 return rv;
1102 }
1103
1104 disk_cache::EntryResult entry_result = disk_cache_->CreateEntry(
1105 key, transaction->priority(),
1106 base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
1107 pending_op));
1108 rv = entry_result.net_error();
1109 if (rv == ERR_IO_PENDING) {
1110 pending_op->callback_will_delete = true;
1111 return ERR_IO_PENDING;
1112 }
1113
1114 pending_op->writer->ClearTransaction();
1115 OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
1116 std::move(entry_result));
1117 return rv;
1118 }
1119
AddTransactionToEntry(scoped_refptr<ActiveEntry> & entry,Transaction * transaction)1120 int HttpCache::AddTransactionToEntry(scoped_refptr<ActiveEntry>& entry,
1121 Transaction* transaction) {
1122 DCHECK(entry);
1123 DCHECK(entry->GetEntry());
1124 // Always add a new transaction to the queue to maintain FIFO order.
1125 entry->add_to_entry_queue().push_back(transaction);
1126 // Don't process the transaction if the lock timeout handling is being tested.
1127 if (!bypass_lock_for_test_) {
1128 ProcessQueuedTransactions(entry);
1129 }
1130 return ERR_IO_PENDING;
1131 }
1132
DoneWithResponseHeaders(scoped_refptr<ActiveEntry> & entry,Transaction * transaction,bool is_partial)1133 int HttpCache::DoneWithResponseHeaders(scoped_refptr<ActiveEntry>& entry,
1134 Transaction* transaction,
1135 bool is_partial) {
1136 // If |transaction| is the current writer, do nothing. This can happen for
1137 // range requests since they can go back to headers phase after starting to
1138 // write.
1139 if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
1140 DCHECK(is_partial && entry->writers()->GetTransactionsCount() == 1);
1141 return OK;
1142 }
1143
1144 DCHECK_EQ(entry->headers_transaction(), transaction);
1145
1146 entry->ClearHeadersTransaction();
1147
1148 // If transaction is responsible for writing the response body, then do not go
1149 // through done_headers_queue for performance benefit. (Also, in case of
1150 // writer transaction, the consumer sometimes depend on synchronous behaviour
1151 // e.g. while computing raw headers size. (crbug.com/711766))
1152 if ((transaction->mode() & Transaction::WRITE) && !entry->HasWriters() &&
1153 entry->readers().empty()) {
1154 entry->AddTransactionToWriters(
1155 transaction, CanTransactionJoinExistingWriters(transaction));
1156 ProcessQueuedTransactions(entry);
1157 return OK;
1158 }
1159
1160 entry->done_headers_queue().push_back(transaction);
1161 ProcessQueuedTransactions(entry);
1162 return ERR_IO_PENDING;
1163 }
1164
DoneWithEntry(scoped_refptr<ActiveEntry> & entry,Transaction * transaction,bool entry_is_complete,bool is_partial)1165 void HttpCache::DoneWithEntry(scoped_refptr<ActiveEntry>& entry,
1166 Transaction* transaction,
1167 bool entry_is_complete,
1168 bool is_partial) {
1169 bool is_mode_read_only = transaction->mode() == Transaction::READ;
1170
1171 if (!entry_is_complete && !is_mode_read_only && is_partial) {
1172 entry->GetEntry()->CancelSparseIO();
1173 }
1174
1175 // Transaction is waiting in the done_headers_queue.
1176 auto it = base::ranges::find(entry->done_headers_queue(), transaction);
1177 if (it != entry->done_headers_queue().end()) {
1178 entry->done_headers_queue().erase(it);
1179
1180 // Restart other transactions if this transaction could have written
1181 // response body.
1182 if (!entry_is_complete && !is_mode_read_only) {
1183 ProcessEntryFailure(entry.get());
1184 }
1185 return;
1186 }
1187
1188 // Transaction is removed in the headers phase.
1189 if (transaction == entry->headers_transaction()) {
1190 entry->ClearHeadersTransaction();
1191
1192 if (entry_is_complete || is_mode_read_only) {
1193 ProcessQueuedTransactions(entry);
1194 } else {
1195 // Restart other transactions if this transaction could have written
1196 // response body.
1197 ProcessEntryFailure(entry.get());
1198 }
1199 return;
1200 }
1201
1202 // Transaction is removed in the writing phase.
1203 if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
1204 entry->writers()->RemoveTransaction(transaction,
1205 entry_is_complete /* success */);
1206 return;
1207 }
1208
1209 // Transaction is reading from the entry.
1210 DCHECK(!entry->HasWriters());
1211 auto readers_it = entry->readers().find(transaction);
1212 CHECK(readers_it != entry->readers().end(), base::NotFatalUntil::M130);
1213 entry->readers().erase(readers_it);
1214 ProcessQueuedTransactions(entry);
1215 }
1216
WritersDoomEntryRestartTransactions(ActiveEntry * entry)1217 void HttpCache::WritersDoomEntryRestartTransactions(ActiveEntry* entry) {
1218 DCHECK(!entry->writers()->IsEmpty());
1219 ProcessEntryFailure(entry);
1220 }
1221
WritersDoneWritingToEntry(scoped_refptr<ActiveEntry> entry,bool success,bool should_keep_entry,TransactionSet make_readers)1222 void HttpCache::WritersDoneWritingToEntry(scoped_refptr<ActiveEntry> entry,
1223 bool success,
1224 bool should_keep_entry,
1225 TransactionSet make_readers) {
1226 // Impacts the queued transactions in one of the following ways:
1227 // - restart them but do not doom the entry since entry can be saved in
1228 // its truncated form.
1229 // - restart them and doom/destroy the entry since entry does not
1230 // have valid contents.
1231 // - let them continue by invoking their callback since entry is
1232 // successfully written.
1233 DCHECK(entry->HasWriters());
1234 DCHECK(entry->writers()->IsEmpty());
1235 DCHECK(success || make_readers.empty());
1236
1237 if (!success && should_keep_entry) {
1238 // Restart already validated transactions so that they are able to read
1239 // the truncated status of the entry.
1240 entry->RestartHeadersPhaseTransactions();
1241 entry->ReleaseWriters();
1242 return;
1243 }
1244
1245 if (success) {
1246 // Add any idle writers to readers.
1247 for (Transaction* reader : make_readers) {
1248 reader->WriteModeTransactionAboutToBecomeReader();
1249 entry->readers().insert(reader);
1250 }
1251 // Reset writers here so that WriteModeTransactionAboutToBecomeReader can
1252 // access the network transaction.
1253 entry->ReleaseWriters();
1254 ProcessQueuedTransactions(std::move(entry));
1255 } else {
1256 entry->ReleaseWriters();
1257 ProcessEntryFailure(entry.get());
1258 }
1259 }
1260
DoomEntryValidationNoMatch(scoped_refptr<ActiveEntry> entry)1261 void HttpCache::DoomEntryValidationNoMatch(scoped_refptr<ActiveEntry> entry) {
1262 // Validating transaction received a non-matching response.
1263 DCHECK(entry->headers_transaction());
1264
1265 entry->ClearHeadersTransaction();
1266
1267 DoomActiveEntry(entry->GetEntry()->GetKey());
1268
1269 // Restart only add_to_entry_queue transactions.
1270 // Post task here to avoid a race in creating the entry between |transaction|
1271 // and the add_to_entry_queue transactions. Reset the queued transaction's
1272 // cache pending state so that in case it's destructor is invoked, it's ok
1273 // for the transaction to not be found in this entry.
1274 for (HttpCache::Transaction* transaction : entry->add_to_entry_queue()) {
1275 transaction->ResetCachePendingState();
1276 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1277 FROM_HERE,
1278 base::BindOnce(transaction->cache_io_callback(), ERR_CACHE_RACE));
1279 }
1280 entry->add_to_entry_queue().clear();
1281 }
1282
ProcessEntryFailure(ActiveEntry * entry)1283 void HttpCache::ProcessEntryFailure(ActiveEntry* entry) {
1284 // The writer failed to completely write the response to
1285 // the cache.
1286
1287 if (entry->headers_transaction()) {
1288 entry->RestartHeadersTransaction();
1289 }
1290
1291 TransactionList list = entry->TakeAllQueuedTransactions();
1292
1293 DoomActiveEntry(entry->GetEntry()->GetKey());
1294
1295 // ERR_CACHE_RACE causes the transaction to restart the whole process.
1296 for (Transaction* queued_transaction : list) {
1297 queued_transaction->cache_io_callback().Run(ERR_CACHE_RACE);
1298 }
1299 }
1300
ProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry)1301 void HttpCache::ProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
1302 // Multiple readers may finish with an entry at once, so we want to batch up
1303 // calls to OnProcessQueuedTransactions. This flag also tells us that we
1304 // should not delete the entry before OnProcessQueuedTransactions runs.
1305 if (entry->will_process_queued_transactions()) {
1306 return;
1307 }
1308
1309 entry->set_will_process_queued_transactions(true);
1310
1311 // Post a task instead of invoking the io callback of another transaction here
1312 // to avoid re-entrancy.
1313 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1314 FROM_HERE, base::BindOnce(&HttpCache::OnProcessQueuedTransactions,
1315 GetWeakPtr(), std::move(entry)));
1316 }
1317
ProcessAddToEntryQueue(scoped_refptr<ActiveEntry> entry)1318 void HttpCache::ProcessAddToEntryQueue(scoped_refptr<ActiveEntry> entry) {
1319 CHECK(!entry->add_to_entry_queue().empty());
1320 if (delay_add_transaction_to_entry_for_test_) {
1321 // Post a task to put the AddTransactionToEntry handling at the back of
1322 // the task queue. This allows other tasks (like network IO) to jump
1323 // ahead and simulate different callback ordering for testing.
1324 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1325 FROM_HERE, base::BindOnce(&HttpCache::ProcessAddToEntryQueueImpl,
1326 GetWeakPtr(), std::move(entry)));
1327 } else {
1328 entry->ProcessAddToEntryQueue();
1329 }
1330 }
1331
ProcessAddToEntryQueueImpl(scoped_refptr<ActiveEntry> entry)1332 void HttpCache::ProcessAddToEntryQueueImpl(scoped_refptr<ActiveEntry> entry) {
1333 entry->ProcessAddToEntryQueue();
1334 }
1335
CanTransactionJoinExistingWriters(Transaction * transaction)1336 HttpCache::ParallelWritingPattern HttpCache::CanTransactionJoinExistingWriters(
1337 Transaction* transaction) {
1338 if (transaction->method() != "GET") {
1339 return PARALLEL_WRITING_NOT_JOIN_METHOD_NOT_GET;
1340 }
1341 if (transaction->partial()) {
1342 return PARALLEL_WRITING_NOT_JOIN_RANGE;
1343 }
1344 if (transaction->mode() == Transaction::READ) {
1345 return PARALLEL_WRITING_NOT_JOIN_READ_ONLY;
1346 }
1347 if (transaction->GetResponseInfo()->headers &&
1348 transaction->GetResponseInfo()->headers->GetContentLength() >
1349 disk_cache_->MaxFileSize()) {
1350 return PARALLEL_WRITING_NOT_JOIN_TOO_BIG_FOR_CACHE;
1351 }
1352 return PARALLEL_WRITING_JOIN;
1353 }
1354
ProcessDoneHeadersQueue(scoped_refptr<ActiveEntry> entry)1355 void HttpCache::ProcessDoneHeadersQueue(scoped_refptr<ActiveEntry> entry) {
1356 ParallelWritingPattern writers_pattern;
1357 DCHECK(!entry->HasWriters() ||
1358 entry->writers()->CanAddWriters(&writers_pattern));
1359 DCHECK(!entry->done_headers_queue().empty());
1360
1361 Transaction* transaction = entry->done_headers_queue().front();
1362
1363 ParallelWritingPattern parallel_writing_pattern =
1364 CanTransactionJoinExistingWriters(transaction);
1365 if (entry->IsWritingInProgress()) {
1366 if (parallel_writing_pattern != PARALLEL_WRITING_JOIN) {
1367 // TODO(shivanisha): Returning from here instead of checking the next
1368 // transaction in the queue because the FIFO order is maintained
1369 // throughout, until it becomes a reader or writer. May be at this point
1370 // the ordering is not important but that would be optimizing a rare
1371 // scenario where write mode transactions are insterspersed with read-only
1372 // transactions.
1373 return;
1374 }
1375 entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
1376 } else { // no writing in progress
1377 if (transaction->mode() & Transaction::WRITE) {
1378 if (transaction->partial()) {
1379 if (entry->readers().empty()) {
1380 entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
1381 } else {
1382 return;
1383 }
1384 } else {
1385 // Add the transaction to readers since the response body should have
1386 // already been written. (If it was the first writer about to start
1387 // writing to the cache, it would have been added to writers in
1388 // DoneWithResponseHeaders, thus no writers here signify the response
1389 // was completely written).
1390 transaction->WriteModeTransactionAboutToBecomeReader();
1391 auto return_val = entry->readers().insert(transaction);
1392 DCHECK(return_val.second);
1393 }
1394 } else { // mode READ
1395 auto return_val = entry->readers().insert(transaction);
1396 DCHECK(return_val.second);
1397 }
1398 }
1399
1400 // Post another task to give a chance to more transactions to either join
1401 // readers or another transaction to start parallel validation.
1402 ProcessQueuedTransactions(entry);
1403
1404 entry->done_headers_queue().erase(entry->done_headers_queue().begin());
1405 transaction->cache_io_callback().Run(OK);
1406 }
1407
GetLoadStateForPendingTransaction(const Transaction * transaction)1408 LoadState HttpCache::GetLoadStateForPendingTransaction(
1409 const Transaction* transaction) {
1410 auto i = active_entries_.find(transaction->key());
1411 if (i == active_entries_.end()) {
1412 // If this is really a pending transaction, and it is not part of
1413 // active_entries_, we should be creating the backend or the entry.
1414 return LOAD_STATE_WAITING_FOR_CACHE;
1415 }
1416
1417 Writers* writers = i->second->writers();
1418 return !writers ? LOAD_STATE_WAITING_FOR_CACHE : writers->GetLoadState();
1419 }
1420
RemovePendingTransaction(Transaction * transaction)1421 void HttpCache::RemovePendingTransaction(Transaction* transaction) {
1422 auto i = active_entries_.find(transaction->key());
1423 bool found = false;
1424 if (i != active_entries_.end()) {
1425 found = i->second->RemovePendingTransaction(transaction);
1426 }
1427
1428 if (found) {
1429 return;
1430 }
1431
1432 if (building_backend_) {
1433 auto j = pending_ops_.find(std::string());
1434 if (j != pending_ops_.end()) {
1435 found = RemovePendingTransactionFromPendingOp(j->second, transaction);
1436 }
1437
1438 if (found) {
1439 return;
1440 }
1441 }
1442
1443 auto j = pending_ops_.find(transaction->key());
1444 if (j != pending_ops_.end()) {
1445 found = RemovePendingTransactionFromPendingOp(j->second, transaction);
1446 }
1447
1448 if (found) {
1449 return;
1450 }
1451
1452 for (auto k = doomed_entries_.begin(); k != doomed_entries_.end() && !found;
1453 ++k) {
1454 // TODO(ricea): Add unit test for this line.
1455 found = k->get().RemovePendingTransaction(transaction);
1456 }
1457
1458 DCHECK(found) << "Pending transaction not found";
1459 }
1460
RemovePendingTransactionFromPendingOp(PendingOp * pending_op,Transaction * transaction)1461 bool HttpCache::RemovePendingTransactionFromPendingOp(
1462 PendingOp* pending_op,
1463 Transaction* transaction) {
1464 if (pending_op->writer->Matches(transaction)) {
1465 pending_op->writer->ClearTransaction();
1466 pending_op->writer->ClearEntry();
1467 return true;
1468 }
1469 WorkItemList& pending_queue = pending_op->pending_queue;
1470
1471 for (auto it = pending_queue.begin(); it != pending_queue.end(); ++it) {
1472 if ((*it)->Matches(transaction)) {
1473 pending_queue.erase(it);
1474 return true;
1475 }
1476 }
1477 return false;
1478 }
1479
MarkKeyNoStore(const std::string & key)1480 void HttpCache::MarkKeyNoStore(const std::string& key) {
1481 keys_marked_no_store_.Put(base::SHA1Hash(base::as_byte_span(key)));
1482 }
1483
DidKeyLeadToNoStoreResponse(const std::string & key)1484 bool HttpCache::DidKeyLeadToNoStoreResponse(const std::string& key) {
1485 return keys_marked_no_store_.Get(base::SHA1Hash(base::as_byte_span(key))) !=
1486 keys_marked_no_store_.end();
1487 }
1488
OnProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry)1489 void HttpCache::OnProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
1490 entry->set_will_process_queued_transactions(false);
1491
1492 // Note that this function should only invoke one transaction's IO callback
1493 // since its possible for IO callbacks' consumers to destroy the cache/entry.
1494
1495 if (entry->done_headers_queue().empty() &&
1496 entry->add_to_entry_queue().empty()) {
1497 return;
1498 }
1499
1500 // To maintain FIFO order of transactions, done_headers_queue should be
1501 // checked for processing before add_to_entry_queue.
1502
1503 // If another transaction is writing the response, let validated transactions
1504 // wait till the response is complete. If the response is not yet started, the
1505 // done_headers_queue transaction should start writing it.
1506 if (!entry->done_headers_queue().empty()) {
1507 ParallelWritingPattern unused_reason;
1508 if (!entry->writers() || entry->writers()->CanAddWriters(&unused_reason)) {
1509 ProcessDoneHeadersQueue(entry);
1510 return;
1511 }
1512 }
1513
1514 if (!entry->add_to_entry_queue().empty()) {
1515 ProcessAddToEntryQueue(std::move(entry));
1516 }
1517 }
1518
OnIOComplete(int result,PendingOp * pending_op)1519 void HttpCache::OnIOComplete(int result, PendingOp* pending_op) {
1520 WorkItemOperation op = pending_op->writer->operation();
1521
1522 // Completing the creation of the backend is simpler than the other cases.
1523 if (op == WI_CREATE_BACKEND) {
1524 return OnBackendCreated(result, pending_op);
1525 }
1526
1527 std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
1528 bool try_restart_requests = false;
1529
1530 scoped_refptr<ActiveEntry> entry;
1531 std::string key;
1532 if (result == OK) {
1533 if (op == WI_DOOM_ENTRY) {
1534 // Anything after a Doom has to be restarted.
1535 try_restart_requests = true;
1536 } else if (item->IsValid()) {
1537 DCHECK(pending_op->entry);
1538 key = pending_op->entry->GetKey();
1539 entry = ActivateEntry(pending_op->entry, pending_op->entry_opened);
1540 } else {
1541 // The writer transaction is gone.
1542 if (!pending_op->entry_opened) {
1543 pending_op->entry->Doom();
1544 }
1545
1546 pending_op->entry->Close();
1547 pending_op->entry = nullptr;
1548 try_restart_requests = true;
1549 }
1550 }
1551
1552 // We are about to notify a bunch of transactions, and they may decide to
1553 // re-issue a request (or send a different one). If we don't delete
1554 // pending_op, the new request will be appended to the end of the list, and
1555 // we'll see it again from this point before it has a chance to complete (and
1556 // we'll be messing out the request order). The down side is that if for some
1557 // reason notifying request A ends up cancelling request B (for the same key),
1558 // we won't find request B anywhere (because it would be in a local variable
1559 // here) and that's bad. If there is a chance for that to happen, we'll have
1560 // to move the callback used to be a CancelableOnceCallback. By the way, for
1561 // this to happen the action (to cancel B) has to be synchronous to the
1562 // notification for request A.
1563 WorkItemList pending_items = std::move(pending_op->pending_queue);
1564 DeletePendingOp(pending_op);
1565
1566 item->NotifyTransaction(result, entry);
1567
1568 while (!pending_items.empty()) {
1569 item = std::move(pending_items.front());
1570 pending_items.pop_front();
1571
1572 if (item->operation() == WI_DOOM_ENTRY) {
1573 // A queued doom request is always a race.
1574 try_restart_requests = true;
1575 } else if (result == OK) {
1576 entry = GetActiveEntry(key);
1577 if (!entry) {
1578 try_restart_requests = true;
1579 }
1580 }
1581
1582 if (try_restart_requests) {
1583 item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1584 continue;
1585 }
1586 // At this point item->operation() is anything except Doom.
1587 if (item->operation() == WI_CREATE_ENTRY) {
1588 if (result == OK) {
1589 // Successful OpenOrCreate, Open, or Create followed by a Create.
1590 item->NotifyTransaction(ERR_CACHE_CREATE_FAILURE, nullptr);
1591 } else {
1592 if (op != WI_CREATE_ENTRY && op != WI_OPEN_OR_CREATE_ENTRY) {
1593 // Failed Open or Doom followed by a Create.
1594 item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1595 try_restart_requests = true;
1596 } else {
1597 item->NotifyTransaction(result, entry);
1598 }
1599 }
1600 }
1601 // item->operation() is OpenOrCreate or Open
1602 else if (item->operation() == WI_OPEN_OR_CREATE_ENTRY) {
1603 if ((op == WI_OPEN_ENTRY || op == WI_CREATE_ENTRY) && result != OK) {
1604 // Failed Open or Create followed by an OpenOrCreate.
1605 item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1606 try_restart_requests = true;
1607 } else {
1608 item->NotifyTransaction(result, entry);
1609 }
1610 }
1611 // item->operation() is Open.
1612 else {
1613 if (op == WI_CREATE_ENTRY && result != OK) {
1614 // Failed Create followed by an Open.
1615 item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1616 try_restart_requests = true;
1617 } else {
1618 item->NotifyTransaction(result, entry);
1619 }
1620 }
1621 }
1622 }
1623
1624 // static
OnPendingOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,int rv)1625 void HttpCache::OnPendingOpComplete(base::WeakPtr<HttpCache> cache,
1626 PendingOp* pending_op,
1627 int rv) {
1628 if (cache.get()) {
1629 pending_op->callback_will_delete = false;
1630 cache->OnIOComplete(rv, pending_op);
1631 } else {
1632 // The callback was cancelled so we should delete the pending_op that
1633 // was used with this callback.
1634 delete pending_op;
1635 }
1636 }
1637
1638 // static
OnPendingCreationOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,disk_cache::EntryResult result)1639 void HttpCache::OnPendingCreationOpComplete(base::WeakPtr<HttpCache> cache,
1640 PendingOp* pending_op,
1641 disk_cache::EntryResult result) {
1642 if (!cache.get()) {
1643 // The callback was cancelled so we should delete the pending_op that
1644 // was used with this callback. If |result| contains a fresh entry
1645 // it will close it automatically, since we don't release it here.
1646 delete pending_op;
1647 return;
1648 }
1649
1650 int rv = result.net_error();
1651 pending_op->entry_opened = result.opened();
1652 pending_op->entry = result.ReleaseEntry();
1653 pending_op->callback_will_delete = false;
1654 cache->OnIOComplete(rv, pending_op);
1655 }
1656
1657 // static
OnPendingBackendCreationOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,disk_cache::BackendResult result)1658 void HttpCache::OnPendingBackendCreationOpComplete(
1659 base::WeakPtr<HttpCache> cache,
1660 PendingOp* pending_op,
1661 disk_cache::BackendResult result) {
1662 if (!cache.get()) {
1663 // The callback was cancelled so we should delete the pending_op that
1664 // was used with this callback. If `result` contains a cache backend,
1665 // it will be destroyed with it.
1666 delete pending_op;
1667 return;
1668 }
1669
1670 int rv = result.net_error;
1671 pending_op->backend = std::move(result.backend);
1672 pending_op->callback_will_delete = false;
1673 cache->OnIOComplete(rv, pending_op);
1674 }
1675
OnBackendCreated(int result,PendingOp * pending_op)1676 void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) {
1677 std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
1678 WorkItemOperation op = item->operation();
1679 DCHECK_EQ(WI_CREATE_BACKEND, op);
1680
1681 if (backend_factory_.get()) {
1682 // We may end up calling OnBackendCreated multiple times if we have pending
1683 // work items. The first call saves the backend and releases the factory,
1684 // and the last call clears building_backend_.
1685 backend_factory_.reset(); // Reclaim memory.
1686 if (result == OK) {
1687 disk_cache_ = std::move(pending_op->backend);
1688 UMA_HISTOGRAM_MEMORY_KB("HttpCache.MaxFileSizeOnInit",
1689 disk_cache_->MaxFileSize() / 1024);
1690 }
1691 }
1692
1693 if (!pending_op->pending_queue.empty()) {
1694 std::unique_ptr<WorkItem> pending_item =
1695 std::move(pending_op->pending_queue.front());
1696 pending_op->pending_queue.pop_front();
1697 DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation());
1698
1699 // We want to process a single callback at a time, because the cache may
1700 // go away from the callback.
1701 pending_op->writer = std::move(pending_item);
1702
1703 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1704 FROM_HERE, base::BindOnce(&HttpCache::OnBackendCreated, GetWeakPtr(),
1705 result, pending_op));
1706 } else {
1707 building_backend_ = false;
1708 DeletePendingOp(pending_op);
1709 }
1710
1711 // The cache may be gone when we return from the callback.
1712 if (!item->DoCallback(result)) {
1713 item->NotifyTransaction(result, nullptr);
1714 }
1715 }
1716
1717 } // namespace net
1718