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