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