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