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