Index: content/browser/appcache/appcache_update_url_fetcher.cc |
diff --git a/content/browser/appcache/appcache_update_url_fetcher.cc b/content/browser/appcache/appcache_update_url_fetcher.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..018ffc6eb08d9d8f6488daead39ece7c7df82383 |
--- /dev/null |
+++ b/content/browser/appcache/appcache_update_url_fetcher.cc |
@@ -0,0 +1,305 @@ |
+// 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 "net/http/http_request_headers.h" |
+#include "net/http/http_response_headers.h" |
+#include "net/traffic_annotation/network_traffic_annotation.h" |
+#include "net/url_request/url_request_context.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+const int kMax503Retries = 3; |
+ |
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = |
+ net::DefineNetworkTrafficAnnotation("appcache_update_job", R"( |
+ semantics { |
+ sender: "HTML5 AppCache System" |
+ description: |
+ "Web pages can include a link to a manifest file which lists " |
+ "resources to be cached for offline access. The AppCache system" |
+ "retrieves those resources in the background." |
+ trigger: |
+ "User visits a web page containing a <html manifest=manifestUrl> " |
+ "tag, or navigates to a document retrieved from an existing appcache " |
+ "and some resource should be updated." |
+ data: "None" |
+ destination: WEBSITE |
+ } |
+ policy { |
+ cookies_allowed: true |
+ cookies_store: "user" |
+ setting: |
+ "Users can control this feature via the 'Cookies' setting under " |
+ "'Privacy, Content settings'. If cookies are disabled for a single " |
+ "site, appcaches are disabled for the site only. If they are totally " |
+ "disabled, all appcache requests will be stopped." |
+ chrome_policy { |
+ DefaultCookiesSetting { |
+ policy_options {mode: MANDATORY} |
+ DefaultCookiesSetting: 2 |
+ } |
+ } |
+ })"); |
+ |
+} // 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. |
+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), |
+ buffer_(new net::IOBuffer(buffer_size)), |
+ request_( |
+ job->service_->request_context()->CreateRequest(url, |
+ net::DEFAULT_PRIORITY, |
+ this, |
+ kTrafficAnnotation)), |
+ result_(AppCacheUpdateJob::UPDATE_OK), |
+ redirect_response_code_(-1), |
+ buffer_size_(buffer_size) {} |
+ |
+URLFetcher::~URLFetcher() { |
+ // To defend against URLRequest calling delegate methods during |
+ // destruction, we test for a !request_ in those methods. |
+ std::unique_ptr<net::URLRequest> temp = std::move(request_); |
+} |
+ |
+void URLFetcher::Start() { |
+ request_->set_first_party_for_cookies(job_->manifest_url_); |
+ request_->set_initiator(url::Origin(job_->manifest_url_)); |
+ if (fetch_type_ == MANIFEST_FETCH && job_->doing_full_update_check_) |
+ request_->SetLoadFlags(request_->load_flags() | net::LOAD_BYPASS_CACHE); |
+ else if (existing_response_headers_.get()) |
+ AddConditionalHeaders(existing_response_headers_.get()); |
+ request_->Start(); |
+} |
+ |
+void URLFetcher::OnReceivedRedirect(net::URLRequest* request, |
+ const net::RedirectInfo& redirect_info, |
+ bool* defer_redirect) { |
+ if (!request_) |
+ return; |
+ DCHECK_EQ(request_.get(), 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 URLFetcher::OnResponseStarted(net::URLRequest* request, int net_error) { |
+ if (!request_) |
+ return; |
+ DCHECK_EQ(request_.get(), 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. |
+ const net::HttpNetworkSession::Params* session_params = |
+ request->context()->GetNetworkSessionParams(); |
+ bool ignore_cert_errors = |
+ session_params && session_params->ignore_certificate_errors; |
+ if ((net::IsCertStatusError(request->ssl_info().cert_status) && |
+ !ignore_cert_errors) || |
+ (url_.GetOrigin() != job_->manifest_url_.GetOrigin() && |
+ request->response_headers()->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_.reset(job_->CreateResponseWriter()); |
+ scoped_refptr<HttpResponseInfoIOBuffer> io_buffer( |
+ new HttpResponseInfoIOBuffer( |
+ new net::HttpResponseInfo(request->response_info()))); |
+ response_writer_->WriteInfo( |
+ io_buffer.get(), |
+ base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this))); |
+ } else { |
+ ReadResponseData(); |
+ } |
+} |
+ |
+void URLFetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) { |
+ if (!request_) |
+ return; |
+ DCHECK_NE(net::ERR_IO_PENDING, bytes_read); |
+ DCHECK_EQ(request_.get(), request); |
+ bool data_consumed = true; |
+ if (bytes_read > 0) { |
+ job_->MadeProgress(); |
+ data_consumed = ConsumeResponseData(bytes_read); |
+ if (data_consumed) { |
+ while (true) { |
+ bytes_read = request->Read(buffer_.get(), buffer_size_); |
+ if (bytes_read <= 0) |
+ break; |
+ data_consumed = ConsumeResponseData(bytes_read); |
+ if (!data_consumed) |
+ break; |
+ } |
+ } |
+ } |
+ |
+ if (data_consumed && bytes_read != net::ERR_IO_PENDING) { |
+ DCHECK_EQ(AppCacheUpdateJob::UPDATE_OK, result_); |
+ OnResponseCompleted(bytes_read); |
+ } |
+} |
+ |
+void 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 URLFetcher::OnWriteComplete(int result) { |
+ if (result < 0) { |
+ request_->Cancel(); |
+ result_ = AppCacheUpdateJob::DISKCACHE_ERROR; |
+ OnResponseCompleted(net::ERR_ABORTED); |
+ return; |
+ } |
+ ReadResponseData(); |
+} |
+ |
+void URLFetcher::ReadResponseData() { |
+ AppCacheUpdateJob::InternalUpdateState state = job_->internal_state_; |
+ if (state == AppCacheUpdateJob::CACHE_FAILURE || |
+ state == AppCacheUpdateJob::CANCELLED || |
+ state == AppCacheUpdateJob::COMPLETED) { |
+ return; |
+ } |
+ int bytes_read = request_->Read(buffer_.get(), buffer_size_); |
+ if (bytes_read != net::ERR_IO_PENDING) |
+ OnReadCompleted(request_.get(), bytes_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 URLFetcher::ConsumeResponseData(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_.get(), bytes_read, |
+ base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this))); |
+ return false; // wait for async write completion to continue reading |
+ default: |
+ NOTREACHED(); |
+ } |
+ return true; |
+} |
+ |
+void URLFetcher::OnResponseCompleted(int net_error) { |
+ if (net_error == net::OK) |
+ job_->MadeProgress(); |
+ |
+ // 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 URLFetcher::MaybeRetryRequest() { |
+ if (retry_503_attempts_ >= kMax503Retries || |
+ !request_->response_headers()->HasHeaderValue("retry-after", "0")) { |
+ return false; |
+ } |
+ ++retry_503_attempts_; |
+ result_ = AppCacheUpdateJob::UPDATE_OK; |
+ request_ = job_->service_->request_context()->CreateRequest( |
+ url_, net::DEFAULT_PRIORITY, this, kTrafficAnnotation); |
+ Start(); |
+ return true; |
+} |
+ |
+} // namespace content. |