| // Copyright (c) 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/appcache/appcache_update_url_fetcher.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "content/browser/appcache/appcache_update_url_loader_request.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const int kMax503Retries = 3; |
| |
| } // namespace |
| |
| // Helper class to fetch resources. Depending on the fetch type, |
| // can either fetch to an in-memory string or write the response |
| // data out to the disk cache. |
| AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url, |
| FetchType fetch_type, |
| AppCacheUpdateJob* job, |
| int buffer_size) |
| : url_(url), |
| job_(job), |
| fetch_type_(fetch_type), |
| retry_503_attempts_(0), |
| request_(std::make_unique<UpdateURLLoaderRequest>( |
| job->service_->url_loader_factory_getter(), |
| url, |
| buffer_size, |
| this)), |
| result_(AppCacheUpdateJob::UPDATE_OK), |
| redirect_response_code_(-1), |
| buffer_size_(buffer_size) {} |
| |
| AppCacheUpdateJob::URLFetcher::~URLFetcher() {} |
| |
| void AppCacheUpdateJob::URLFetcher::Start() { |
| request_->SetSiteForCookies(job_->manifest_url_); |
| request_->SetInitiator(url::Origin::Create(job_->manifest_url_)); |
| if (fetch_type_ == MANIFEST_FETCH && job_->doing_full_update_check_) |
| request_->SetLoadFlags(request_->GetLoadFlags() | net::LOAD_BYPASS_CACHE); |
| else if (existing_response_headers_.get()) |
| AddConditionalHeaders(existing_response_headers_.get()); |
| request_->Start(); |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect( |
| const net::RedirectInfo& redirect_info) { |
| DCHECK(request_); |
| // Redirect is not allowed by the update process. |
| job_->MadeProgress(); |
| redirect_response_code_ = request_->GetResponseCode(); |
| request_->Cancel(); |
| result_ = AppCacheUpdateJob::REDIRECT_ERROR; |
| OnResponseCompleted(net::ERR_ABORTED); |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::OnResponseStarted(int net_error) { |
| DCHECK(request_); |
| DCHECK_NE(net::ERR_IO_PENDING, net_error); |
| |
| int response_code = -1; |
| if (net_error == net::OK) { |
| response_code = request_->GetResponseCode(); |
| job_->MadeProgress(); |
| } |
| |
| if ((response_code / 100) != 2) { |
| if (response_code > 0) |
| result_ = AppCacheUpdateJob::SERVER_ERROR; |
| else |
| result_ = AppCacheUpdateJob::NETWORK_ERROR; |
| OnResponseCompleted(net_error); |
| return; |
| } |
| |
| if (url_.SchemeIsCryptographic()) { |
| // Do not cache content with cert errors. |
| // Also, we willfully violate the HTML5 spec at this point in order |
| // to support the appcaching of cross-origin HTTPS resources. |
| // We've opted for a milder constraint and allow caching unless |
| // the resource has a "no-store" header. A spec change has been |
| // requested on the whatwg list. |
| // See http://code.google.com/p/chromium/issues/detail?id=69594 |
| // TODO(michaeln): Consider doing this for cross-origin HTTP too. |
| if ((net::IsCertStatusError( |
| request_->GetResponseInfo().ssl_info.cert_status) && |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kIgnoreCertificateErrors)) || |
| (url_.GetOrigin() != job_->manifest_url_.GetOrigin() && |
| request_->GetResponseHeaders()->HasHeaderValue("cache-control", |
| "no-store"))) { |
| DCHECK_EQ(-1, redirect_response_code_); |
| request_->Cancel(); |
| result_ = AppCacheUpdateJob::SECURITY_ERROR; |
| OnResponseCompleted(net::ERR_ABORTED); |
| return; |
| } |
| } |
| |
| // Write response info to storage for URL fetches. Wait for async write |
| // completion before reading any response data. |
| if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) { |
| response_writer_ = job_->CreateResponseWriter(); |
| scoped_refptr<HttpResponseInfoIOBuffer> io_buffer = |
| base::MakeRefCounted<HttpResponseInfoIOBuffer>( |
| std::make_unique<net::HttpResponseInfo>( |
| request_->GetResponseInfo())); |
| response_writer_->WriteInfo( |
| io_buffer.get(), |
| base::BindOnce(&URLFetcher::OnWriteComplete, base::Unretained(this))); |
| } else { |
| ReadResponseData(); |
| } |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::OnReadCompleted(net::IOBuffer* buffer, |
| int bytes_read) { |
| DCHECK(request_); |
| DCHECK_NE(net::ERR_IO_PENDING, bytes_read); |
| |
| if (bytes_read <= 0) { |
| OnResponseCompleted(bytes_read); |
| return; |
| } |
| |
| job_->MadeProgress(); |
| if (ConsumeResponseData(buffer, bytes_read)) |
| request_->Read(); |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders( |
| const net::HttpResponseHeaders* headers) { |
| DCHECK(request_); |
| DCHECK(headers); |
| net::HttpRequestHeaders extra_headers; |
| |
| // Add If-Modified-Since header if response info has Last-Modified header. |
| const std::string last_modified = "Last-Modified"; |
| std::string last_modified_value; |
| headers->EnumerateHeader(nullptr, last_modified, &last_modified_value); |
| if (!last_modified_value.empty()) { |
| extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince, |
| last_modified_value); |
| } |
| |
| // Add If-None-Match header if response info has ETag header. |
| const std::string etag = "ETag"; |
| std::string etag_value; |
| headers->EnumerateHeader(nullptr, etag, &etag_value); |
| if (!etag_value.empty()) { |
| extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch, etag_value); |
| } |
| if (!extra_headers.IsEmpty()) |
| request_->SetExtraRequestHeaders(extra_headers); |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) { |
| if (result < 0) { |
| request_->Cancel(); |
| result_ = AppCacheUpdateJob::DISKCACHE_ERROR; |
| OnResponseCompleted(net::ERR_ABORTED); |
| return; |
| } |
| ReadResponseData(); |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::ReadResponseData() { |
| AppCacheUpdateJob::InternalUpdateState state = job_->internal_state_; |
| if (state == AppCacheUpdateJob::CACHE_FAILURE || |
| state == AppCacheUpdateJob::CANCELLED || |
| state == AppCacheUpdateJob::COMPLETED) { |
| return; |
| } |
| request_->Read(); |
| } |
| |
| // Returns false if response data is processed asynchronously, in which |
| // case ReadResponseData will be invoked when it is safe to continue |
| // reading more response data from the request. |
| bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(net::IOBuffer* buffer, |
| int bytes_read) { |
| DCHECK_GT(bytes_read, 0); |
| switch (fetch_type_) { |
| case MANIFEST_FETCH: |
| case MANIFEST_REFETCH: |
| manifest_data_.append(buffer->data(), bytes_read); |
| break; |
| case URL_FETCH: |
| case MASTER_ENTRY_FETCH: |
| DCHECK(response_writer_.get()); |
| response_writer_->WriteData( |
| buffer, bytes_read, |
| base::BindOnce(&URLFetcher::OnWriteComplete, base::Unretained(this))); |
| return false; // wait for async write completion to continue reading |
| default: |
| NOTREACHED(); |
| } |
| return true; |
| } |
| |
| void AppCacheUpdateJob::URLFetcher::OnResponseCompleted(int net_error) { |
| if (net_error == net::OK) { |
| job_->MadeProgress(); |
| } else if (result_ == AppCacheUpdateJob::UPDATE_OK) { |
| result_ = AppCacheUpdateJob::NETWORK_ERROR; |
| } |
| |
| // Retry for 503s where retry-after is 0. |
| if (net_error == net::OK && request_->GetResponseCode() == 503 && |
| MaybeRetryRequest()) { |
| return; |
| } |
| |
| switch (fetch_type_) { |
| case MANIFEST_FETCH: |
| job_->HandleManifestFetchCompleted(this, net_error); |
| break; |
| case URL_FETCH: |
| job_->HandleUrlFetchCompleted(this, net_error); |
| break; |
| case MASTER_ENTRY_FETCH: |
| job_->HandleMasterEntryFetchCompleted(this, net_error); |
| break; |
| case MANIFEST_REFETCH: |
| job_->HandleManifestRefetchCompleted(this, net_error); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| delete this; |
| } |
| |
| bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() { |
| if (retry_503_attempts_ >= kMax503Retries || |
| !request_->GetResponseHeaders()->HasHeaderValue("retry-after", "0")) { |
| return false; |
| } |
| ++retry_503_attempts_; |
| result_ = AppCacheUpdateJob::UPDATE_OK; |
| request_ = std::make_unique<UpdateURLLoaderRequest>( |
| job_->service_->url_loader_factory_getter(), url_, buffer_size_, this); |
| Start(); |
| return true; |
| } |
| |
| } // namespace content. |