• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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 #include "net/http/http_cache_writers.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/functional/bind.h"
11 #include "base/functional/callback_helpers.h"
12 #include "base/logging.h"
13 #include "base/not_fatal_until.h"
14 #include "base/task/single_thread_task_runner.h"
15 #include "net/base/net_errors.h"
16 #include "net/disk_cache/disk_cache.h"
17 #include "net/http/http_cache_transaction.h"
18 #include "net/http/http_response_info.h"
19 #include "net/http/http_status_code.h"
20 #include "net/http/partial_data.h"
21 
22 namespace net {
23 
24 namespace {
25 
IsValidResponseForWriter(bool is_partial,const HttpResponseInfo * response_info)26 bool IsValidResponseForWriter(bool is_partial,
27                               const HttpResponseInfo* response_info) {
28   if (!response_info->headers.get()) {
29     return false;
30   }
31 
32   // Return false if the response code sent by the server is garbled.
33   // Both 200 and 304 are valid since concurrent writing is supported.
34   if (!is_partial &&
35       (response_info->headers->response_code() != HTTP_OK &&
36        response_info->headers->response_code() != HTTP_NOT_MODIFIED)) {
37     return false;
38   }
39 
40   return true;
41 }
42 
43 }  // namespace
44 
TransactionInfo(PartialData * partial_data,const bool is_truncated,HttpResponseInfo info)45 HttpCache::Writers::TransactionInfo::TransactionInfo(PartialData* partial_data,
46                                                      const bool is_truncated,
47                                                      HttpResponseInfo info)
48     : partial(partial_data), truncated(is_truncated), response_info(info) {}
49 
50 HttpCache::Writers::TransactionInfo::~TransactionInfo() = default;
51 
52 HttpCache::Writers::TransactionInfo::TransactionInfo(const TransactionInfo&) =
53     default;
54 
Writers(HttpCache * cache,scoped_refptr<HttpCache::ActiveEntry> entry)55 HttpCache::Writers::Writers(HttpCache* cache,
56                             scoped_refptr<HttpCache::ActiveEntry> entry)
57     : cache_(cache), entry_(entry) {
58   DCHECK(cache_);
59   DCHECK(entry_);
60 }
61 
62 HttpCache::Writers::~Writers() = default;
63 
Read(scoped_refptr<IOBuffer> buf,int buf_len,CompletionOnceCallback callback,Transaction * transaction)64 int HttpCache::Writers::Read(scoped_refptr<IOBuffer> buf,
65                              int buf_len,
66                              CompletionOnceCallback callback,
67                              Transaction* transaction) {
68   DCHECK(buf);
69   DCHECK_GT(buf_len, 0);
70   DCHECK(!callback.is_null());
71   DCHECK(transaction);
72 
73   // If another transaction invoked a Read which is currently ongoing, then
74   // this transaction waits for the read to complete and gets its buffer filled
75   // with the data returned from that read.
76   if (next_state_ != State::NONE) {
77     WaitingForRead read_info(buf, buf_len, std::move(callback));
78     waiting_for_read_.emplace(transaction, std::move(read_info));
79     return ERR_IO_PENDING;
80   }
81 
82   DCHECK_EQ(next_state_, State::NONE);
83   DCHECK(callback_.is_null());
84   DCHECK_EQ(nullptr, active_transaction_);
85   DCHECK(HasTransaction(transaction));
86   active_transaction_ = transaction;
87 
88   read_buf_ = std::move(buf);
89   io_buf_len_ = buf_len;
90   next_state_ = State::NETWORK_READ;
91 
92   int rv = DoLoop(OK);
93   if (rv == ERR_IO_PENDING) {
94     callback_ = std::move(callback);
95   }
96 
97   return rv;
98 }
99 
StopCaching(bool keep_entry)100 bool HttpCache::Writers::StopCaching(bool keep_entry) {
101   // If this is the only transaction in Writers, then stopping will be
102   // successful. If not, then we will not stop caching since there are
103   // other consumers waiting to read from the cache.
104   if (all_writers_.size() != 1) {
105     return false;
106   }
107 
108   network_read_only_ = true;
109   if (!keep_entry) {
110     should_keep_entry_ = false;
111     cache_->WritersDoomEntryRestartTransactions(entry_.get());
112   }
113 
114   return true;
115 }
116 
AddTransaction(Transaction * transaction,ParallelWritingPattern initial_writing_pattern,RequestPriority priority,const TransactionInfo & info)117 void HttpCache::Writers::AddTransaction(
118     Transaction* transaction,
119     ParallelWritingPattern initial_writing_pattern,
120     RequestPriority priority,
121     const TransactionInfo& info) {
122   DCHECK(transaction);
123   ParallelWritingPattern writers_pattern;
124   DCHECK(CanAddWriters(&writers_pattern));
125 
126   DCHECK_EQ(0u, all_writers_.count(transaction));
127 
128   // Set truncation related information.
129   response_info_truncation_ = info.response_info;
130   should_keep_entry_ =
131       IsValidResponseForWriter(info.partial != nullptr, &(info.response_info));
132 
133   if (all_writers_.empty()) {
134     DCHECK_EQ(PARALLEL_WRITING_NONE, parallel_writing_pattern_);
135     parallel_writing_pattern_ = initial_writing_pattern;
136     if (parallel_writing_pattern_ != PARALLEL_WRITING_JOIN) {
137       is_exclusive_ = true;
138     }
139   } else {
140     DCHECK_EQ(PARALLEL_WRITING_JOIN, parallel_writing_pattern_);
141   }
142 
143   if (info.partial && !info.truncated) {
144     DCHECK(!partial_do_not_truncate_);
145     partial_do_not_truncate_ = true;
146   }
147 
148   std::pair<Transaction*, TransactionInfo> writer(transaction, info);
149   all_writers_.insert(writer);
150 
151   priority_ = std::max(priority, priority_);
152   if (network_transaction_) {
153     network_transaction_->SetPriority(priority_);
154   }
155 }
156 
SetNetworkTransaction(Transaction * transaction,std::unique_ptr<HttpTransaction> network_transaction)157 void HttpCache::Writers::SetNetworkTransaction(
158     Transaction* transaction,
159     std::unique_ptr<HttpTransaction> network_transaction) {
160   DCHECK_EQ(1u, all_writers_.count(transaction));
161   DCHECK(network_transaction);
162   DCHECK(!network_transaction_);
163   network_transaction_ = std::move(network_transaction);
164   network_transaction_->SetPriority(priority_);
165 }
166 
ResetNetworkTransaction()167 void HttpCache::Writers::ResetNetworkTransaction() {
168   DCHECK(is_exclusive_);
169   DCHECK_EQ(1u, all_writers_.size());
170   DCHECK(all_writers_.begin()->second.partial);
171   network_transaction_.reset();
172 }
173 
RemoveTransaction(Transaction * transaction,bool success)174 void HttpCache::Writers::RemoveTransaction(Transaction* transaction,
175                                            bool success) {
176   EraseTransaction(transaction, OK);
177 
178   if (!all_writers_.empty()) {
179     return;
180   }
181 
182   if (!success && ShouldTruncate()) {
183     TruncateEntry();
184   }
185 
186   // Destroys `this`.
187   cache_->WritersDoneWritingToEntry(entry_, success, should_keep_entry_,
188                                     TransactionSet());
189 }
190 
EraseTransaction(Transaction * transaction,int result)191 void HttpCache::Writers::EraseTransaction(Transaction* transaction,
192                                           int result) {
193   // The transaction should be part of all_writers.
194   auto it = all_writers_.find(transaction);
195   CHECK(it != all_writers_.end(), base::NotFatalUntil::M130);
196   EraseTransaction(it, result);
197 }
198 
199 HttpCache::Writers::TransactionMap::iterator
EraseTransaction(TransactionMap::iterator it,int result)200 HttpCache::Writers::EraseTransaction(TransactionMap::iterator it, int result) {
201   Transaction* transaction = it->first;
202   transaction->WriterAboutToBeRemovedFromEntry(result);
203 
204   auto return_it = all_writers_.erase(it);
205 
206   if (all_writers_.empty() && next_state_ == State::NONE) {
207     // This needs to be called to handle the edge case where even before Read is
208     // invoked all transactions are removed. In that case the
209     // network_transaction_ will still have a valid request info and so it
210     // should be destroyed before its consumer is destroyed (request info
211     // is a raw pointer owned by its consumer).
212     network_transaction_.reset();
213   } else {
214     UpdatePriority();
215   }
216 
217   if (active_transaction_ == transaction) {
218     active_transaction_ = nullptr;
219   } else {
220     // If waiting for read, remove it from the map.
221     waiting_for_read_.erase(transaction);
222   }
223   return return_it;
224 }
225 
UpdatePriority()226 void HttpCache::Writers::UpdatePriority() {
227   // Get the current highest priority.
228   RequestPriority current_highest = MINIMUM_PRIORITY;
229   for (auto& writer : all_writers_) {
230     Transaction* transaction = writer.first;
231     current_highest = std::max(transaction->priority(), current_highest);
232   }
233 
234   if (priority_ != current_highest) {
235     if (network_transaction_) {
236       network_transaction_->SetPriority(current_highest);
237     }
238     priority_ = current_highest;
239   }
240 }
241 
CloseConnectionOnDestruction()242 void HttpCache::Writers::CloseConnectionOnDestruction() {
243   if (network_transaction_) {
244     network_transaction_->CloseConnectionOnDestruction();
245   }
246 }
247 
ContainsOnlyIdleWriters() const248 bool HttpCache::Writers::ContainsOnlyIdleWriters() const {
249   return waiting_for_read_.empty() && !active_transaction_;
250 }
251 
CanAddWriters(ParallelWritingPattern * reason)252 bool HttpCache::Writers::CanAddWriters(ParallelWritingPattern* reason) {
253   *reason = parallel_writing_pattern_;
254 
255   if (all_writers_.empty()) {
256     return true;
257   }
258 
259   return !is_exclusive_ && !network_read_only_;
260 }
261 
ProcessFailure(int error)262 void HttpCache::Writers::ProcessFailure(int error) {
263   // Notify waiting_for_read_ of the failure. Tasks will be posted for all the
264   // transactions.
265   CompleteWaitingForReadTransactions(error);
266 
267   // Idle readers should fail when Read is invoked on them.
268   RemoveIdleWriters(error);
269 }
270 
TruncateEntry()271 void HttpCache::Writers::TruncateEntry() {
272   DCHECK(ShouldTruncate());
273   auto data = base::MakeRefCounted<PickledIOBuffer>();
274   response_info_truncation_.Persist(data->pickle(),
275                                     true /* skip_transient_headers*/,
276                                     true /* response_truncated */);
277   data->Done();
278   io_buf_len_ = data->pickle()->size();
279   entry_->GetEntry()->WriteData(kResponseInfoIndex, 0, data.get(), io_buf_len_,
280                                 base::DoNothing(), true);
281 }
282 
ShouldTruncate()283 bool HttpCache::Writers::ShouldTruncate() {
284   // Don't set the flag for sparse entries or for entries that cannot be
285   // resumed.
286   if (!should_keep_entry_ || partial_do_not_truncate_) {
287     return false;
288   }
289 
290   // Check the response headers for strong validators.
291   // Note that if this is a 206, content-length was already fixed after calling
292   // PartialData::ResponseHeadersOK().
293   if (response_info_truncation_.headers->GetContentLength() <= 0 ||
294       response_info_truncation_.headers->HasHeaderValue("Accept-Ranges",
295                                                         "none") ||
296       !response_info_truncation_.headers->HasStrongValidators()) {
297     should_keep_entry_ = false;
298     return false;
299   }
300 
301   // Double check that there is something worth keeping.
302   int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
303   if (!current_size) {
304     should_keep_entry_ = false;
305     return false;
306   }
307 
308   if (response_info_truncation_.headers->HasHeader("Content-Encoding")) {
309     should_keep_entry_ = false;
310     return false;
311   }
312 
313   int64_t content_length =
314       response_info_truncation_.headers->GetContentLength();
315   if (content_length >= 0 && content_length <= current_size) {
316     return false;
317   }
318 
319   return true;
320 }
321 
GetLoadState() const322 LoadState HttpCache::Writers::GetLoadState() const {
323   if (network_transaction_) {
324     return network_transaction_->GetLoadState();
325   }
326   return LOAD_STATE_IDLE;
327 }
328 
WaitingForRead(scoped_refptr<IOBuffer> buf,int len,CompletionOnceCallback consumer_callback)329 HttpCache::Writers::WaitingForRead::WaitingForRead(
330     scoped_refptr<IOBuffer> buf,
331     int len,
332     CompletionOnceCallback consumer_callback)
333     : read_buf(std::move(buf)),
334       read_buf_len(len),
335       callback(std::move(consumer_callback)) {
336   DCHECK(read_buf);
337   DCHECK_GT(len, 0);
338   DCHECK(!callback.is_null());
339 }
340 
341 HttpCache::Writers::WaitingForRead::~WaitingForRead() = default;
342 HttpCache::Writers::WaitingForRead::WaitingForRead(WaitingForRead&&) = default;
343 
DoLoop(int result)344 int HttpCache::Writers::DoLoop(int result) {
345   DCHECK_NE(State::UNSET, next_state_);
346   DCHECK_NE(State::NONE, next_state_);
347 
348   int rv = result;
349   do {
350     State state = next_state_;
351     next_state_ = State::UNSET;
352     switch (state) {
353       case State::NETWORK_READ:
354         DCHECK_EQ(OK, rv);
355         rv = DoNetworkRead();
356         break;
357       case State::NETWORK_READ_COMPLETE:
358         rv = DoNetworkReadComplete(rv);
359         break;
360       case State::CACHE_WRITE_DATA:
361         rv = DoCacheWriteData(rv);
362         break;
363       case State::CACHE_WRITE_DATA_COMPLETE:
364         rv = DoCacheWriteDataComplete(rv);
365         break;
366       case State::UNSET:
367         NOTREACHED() << "bad state";
368       case State::NONE:
369         // Do Nothing.
370         break;
371     }
372   } while (next_state_ != State::NONE && rv != ERR_IO_PENDING);
373 
374   if (next_state_ != State::NONE) {
375     if (rv != ERR_IO_PENDING && !callback_.is_null()) {
376       std::move(callback_).Run(rv);
377     }
378     return rv;
379   }
380 
381   // Save the callback as |this| may be destroyed when |cache_callback_| is run.
382   // Note that |callback_| is intentionally reset even if it is not run.
383   CompletionOnceCallback callback = std::move(callback_);
384   read_buf_ = nullptr;
385   DCHECK(!all_writers_.empty() || cache_callback_);
386   if (cache_callback_) {
387     std::move(cache_callback_).Run();
388   }
389   // |this| may have been destroyed in the |cache_callback_|.
390   if (rv != ERR_IO_PENDING && !callback.is_null()) {
391     std::move(callback).Run(rv);
392   }
393   return rv;
394 }
395 
DoNetworkRead()396 int HttpCache::Writers::DoNetworkRead() {
397   DCHECK(network_transaction_);
398   next_state_ = State::NETWORK_READ_COMPLETE;
399 
400   // TODO(crbug.com/40089413): This is a partial mitigation. When
401   // reading from the network, a valid HttpNetworkTransaction must be always
402   // available.
403   if (!network_transaction_) {
404     return ERR_FAILED;
405   }
406 
407   CompletionOnceCallback io_callback = base::BindOnce(
408       &HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr());
409   return network_transaction_->Read(read_buf_.get(), io_buf_len_,
410                                     std::move(io_callback));
411 }
412 
DoNetworkReadComplete(int result)413 int HttpCache::Writers::DoNetworkReadComplete(int result) {
414   if (result < 0) {
415     next_state_ = State::NONE;
416     OnNetworkReadFailure(result);
417     return result;
418   }
419 
420   next_state_ = State::CACHE_WRITE_DATA;
421   return result;
422 }
423 
OnNetworkReadFailure(int result)424 void HttpCache::Writers::OnNetworkReadFailure(int result) {
425   ProcessFailure(result);
426 
427   if (active_transaction_) {
428     EraseTransaction(active_transaction_, result);
429   }
430   active_transaction_ = nullptr;
431 
432   if (ShouldTruncate()) {
433     TruncateEntry();
434   }
435 
436   SetCacheCallback(false, TransactionSet());
437 }
438 
DoCacheWriteData(int num_bytes)439 int HttpCache::Writers::DoCacheWriteData(int num_bytes) {
440   next_state_ = State::CACHE_WRITE_DATA_COMPLETE;
441   write_len_ = num_bytes;
442   if (!num_bytes || network_read_only_) {
443     return num_bytes;
444   }
445 
446   int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
447   CompletionOnceCallback io_callback = base::BindOnce(
448       &HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr());
449 
450   int rv = 0;
451 
452   PartialData* partial = nullptr;
453   // The active transaction must be alive if this is a partial request, as
454   // partial requests are exclusive and hence will always be the active
455   // transaction.
456   // TODO(shivanisha): When partial requests support parallel writing, this
457   // assumption will not be true.
458   if (active_transaction_) {
459     partial = all_writers_.find(active_transaction_)->second.partial;
460   }
461 
462   if (!partial) {
463     last_disk_cache_access_start_time_ = base::TimeTicks::Now();
464     rv = entry_->GetEntry()->WriteData(kResponseContentIndex, current_size,
465                                        read_buf_.get(), num_bytes,
466                                        std::move(io_callback), true);
467   } else {
468     rv = partial->CacheWrite(entry_->GetEntry(), read_buf_.get(), num_bytes,
469                              std::move(io_callback));
470   }
471   return rv;
472 }
473 
DoCacheWriteDataComplete(int result)474 int HttpCache::Writers::DoCacheWriteDataComplete(int result) {
475   DCHECK(!all_writers_.empty());
476   DCHECK_GE(write_len_, 0);
477 
478   if (result != write_len_) {
479     next_state_ = State::NONE;
480 
481     // Note that it is possible for cache write to fail if the size of the file
482     // exceeds the per-file limit.
483     OnCacheWriteFailure();
484 
485     // |active_transaction_| can continue reading from the network.
486     return write_len_;
487   }
488 
489   if (!last_disk_cache_access_start_time_.is_null() && active_transaction_ &&
490       !all_writers_.find(active_transaction_)->second.partial) {
491     active_transaction_->AddDiskCacheWriteTime(
492         base::TimeTicks::Now() - last_disk_cache_access_start_time_);
493     last_disk_cache_access_start_time_ = base::TimeTicks();
494   }
495 
496   next_state_ = State::NONE;
497   OnDataReceived(write_len_);
498 
499   return write_len_;
500 }
501 
OnDataReceived(int result)502 void HttpCache::Writers::OnDataReceived(int result) {
503   DCHECK(!all_writers_.empty());
504 
505   auto it = all_writers_.find(active_transaction_);
506   bool is_partial =
507       active_transaction_ != nullptr && it->second.partial != nullptr;
508 
509   // Partial transaction will process the result, return from here.
510   // This is done because partial requests handling require an awareness of both
511   // headers and body state machines as they might have to go to the headers
512   // phase for the next range, so it cannot be completely handled here.
513   if (is_partial) {
514     active_transaction_ = nullptr;
515     return;
516   }
517 
518   if (result == 0) {
519     // Check if the response is actually completed or if not, attempt to mark
520     // the entry as truncated in OnNetworkReadFailure.
521     int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
522     DCHECK(network_transaction_);
523     const HttpResponseInfo* response_info =
524         network_transaction_->GetResponseInfo();
525     int64_t content_length = response_info->headers->GetContentLength();
526     if (content_length >= 0 && content_length > current_size) {
527       OnNetworkReadFailure(result);
528       return;
529     }
530 
531     if (active_transaction_) {
532       EraseTransaction(active_transaction_, result);
533     }
534     active_transaction_ = nullptr;
535     CompleteWaitingForReadTransactions(write_len_);
536 
537     // Invoke entry processing.
538     DCHECK(ContainsOnlyIdleWriters());
539     TransactionSet make_readers;
540     for (auto& writer : all_writers_) {
541       make_readers.insert(writer.first);
542     }
543     all_writers_.clear();
544     SetCacheCallback(true, make_readers);
545     // We assume the set callback will be called immediately.
546     DCHECK_EQ(next_state_, State::NONE);
547     return;
548   }
549 
550   // Notify waiting_for_read_. Tasks will be posted for all the
551   // transactions.
552   CompleteWaitingForReadTransactions(write_len_);
553 
554   active_transaction_ = nullptr;
555 }
556 
OnCacheWriteFailure()557 void HttpCache::Writers::OnCacheWriteFailure() {
558   DLOG(ERROR) << "failed to write response data to cache";
559 
560   ProcessFailure(ERR_CACHE_WRITE_FAILURE);
561 
562   // Now writers will only be reading from the network.
563   network_read_only_ = true;
564 
565   active_transaction_ = nullptr;
566 
567   should_keep_entry_ = false;
568   if (all_writers_.empty()) {
569     SetCacheCallback(false, TransactionSet());
570   } else {
571     cache_->WritersDoomEntryRestartTransactions(entry_.get());
572   }
573 }
574 
CompleteWaitingForReadTransactions(int result)575 void HttpCache::Writers::CompleteWaitingForReadTransactions(int result) {
576   for (auto it = waiting_for_read_.begin(); it != waiting_for_read_.end();) {
577     Transaction* transaction = it->first;
578     int callback_result = result;
579 
580     if (result >= 0) {  // success
581       // Save the data in the waiting transaction's read buffer.
582       it->second.write_len = std::min(it->second.read_buf_len, result);
583       memcpy(it->second.read_buf->data(), read_buf_->data(),
584              it->second.write_len);
585       callback_result = it->second.write_len;
586     }
587 
588     // Post task to notify transaction.
589     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
590         FROM_HERE,
591         base::BindOnce(std::move(it->second.callback), callback_result));
592 
593     it = waiting_for_read_.erase(it);
594 
595     // If its response completion or failure, this transaction needs to be
596     // removed from writers.
597     if (result <= 0) {
598       EraseTransaction(transaction, result);
599     }
600   }
601 }
602 
RemoveIdleWriters(int result)603 void HttpCache::Writers::RemoveIdleWriters(int result) {
604   // Since this is only for idle transactions, waiting_for_read_
605   // should be empty.
606   DCHECK(waiting_for_read_.empty());
607   for (auto it = all_writers_.begin(); it != all_writers_.end();) {
608     Transaction* transaction = it->first;
609     if (transaction == active_transaction_) {
610       it++;
611       continue;
612     }
613     it = EraseTransaction(it, result);
614   }
615 }
616 
SetCacheCallback(bool success,const TransactionSet & make_readers)617 void HttpCache::Writers::SetCacheCallback(bool success,
618                                           const TransactionSet& make_readers) {
619   DCHECK(!cache_callback_);
620   cache_callback_ = base::BindOnce(&HttpCache::WritersDoneWritingToEntry,
621                                    cache_->GetWeakPtr(), entry_, success,
622                                    should_keep_entry_, make_readers);
623 }
624 
OnIOComplete(int result)625 void HttpCache::Writers::OnIOComplete(int result) {
626   DoLoop(result);
627 }
628 
629 }  // namespace net
630