• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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