// 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_url_loader_job.h" #include "base/strings/string_number_conversions.h" #include "content/browser/appcache/appcache_histograms.h" #include "content/browser/appcache/appcache_request_handler.h" #include "content/browser/appcache/appcache_subresource_url_factory.h" #include "content/browser/appcache/appcache_url_loader_request.h" #include "content/browser/url_loader_factory_getter.h" #include "content/public/common/resource_type.h" #include "net/http/http_status_code.h" #include "services/network/public/cpp/net_adapters.h" namespace content { AppCacheURLLoaderJob::~AppCacheURLLoaderJob() { if (storage_.get()) storage_->CancelDelegateCallbacks(this); } bool AppCacheURLLoaderJob::IsStarted() const { return delivery_type_ != AWAITING_DELIVERY_ORDERS && delivery_type_ != NETWORK_DELIVERY; } void AppCacheURLLoaderJob::DeliverAppCachedResponse(const GURL& manifest_url, int64_t cache_id, const AppCacheEntry& entry, bool is_fallback) { if (!storage_.get() || !appcache_request_) { DeliverErrorResponse(); return; } delivery_type_ = APPCACHED_DELIVERY; // In tests we only care about the delivery_type_ state. if (AppCacheRequestHandler::IsRunningInTests()) return; load_timing_info_.request_start_time = base::Time::Now(); load_timing_info_.request_start = base::TimeTicks::Now(); AppCacheHistograms::AddAppCacheJobStartDelaySample(base::TimeTicks::Now() - start_time_tick_); manifest_url_ = manifest_url; cache_id_ = cache_id; entry_ = entry; is_fallback_ = is_fallback; if (is_fallback_ && loader_callback_) CallLoaderCallback(); InitializeRangeRequestInfo(appcache_request_->GetResourceRequest()->headers); storage_->LoadResponseInfo(manifest_url_, entry_.response_id(), this); } void AppCacheURLLoaderJob::DeliverNetworkResponse() { delivery_type_ = NETWORK_DELIVERY; // In tests we only care about the delivery_type_ state. if (AppCacheRequestHandler::IsRunningInTests()) return; AppCacheHistograms::AddNetworkJobStartDelaySample(base::TimeTicks::Now() - start_time_tick_); // We signal our caller with an empy callback that it needs to perform // the network load. DCHECK(loader_callback_ && !binding_.is_bound()); std::move(loader_callback_).Run({}); weak_factory_.InvalidateWeakPtrs(); is_deleting_soon_ = true; base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); } void AppCacheURLLoaderJob::DeliverErrorResponse() { delivery_type_ = ERROR_DELIVERY; // In tests we only care about the delivery_type_ state. if (AppCacheRequestHandler::IsRunningInTests()) return; if (loader_callback_) CallLoaderCallback(); NotifyCompleted(net::ERR_FAILED); AppCacheHistograms::AddErrorJobStartDelaySample(base::TimeTicks::Now() - start_time_tick_); } AppCacheURLLoaderJob* AppCacheURLLoaderJob::AsURLLoaderJob() { return this; } base::WeakPtr AppCacheURLLoaderJob::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } base::WeakPtr AppCacheURLLoaderJob::GetDerivedWeakPtr() { return weak_factory_.GetWeakPtr(); } void AppCacheURLLoaderJob::FollowRedirect( const base::Optional& modified_request_headers) { NOTREACHED() << "appcache never produces redirects"; } void AppCacheURLLoaderJob::ProceedWithResponse() { // TODO(arthursonzogni): Implement this if AppCache starts using the // AppCacheURLLoader before the Network Service has shipped. NOTREACHED(); } void AppCacheURLLoaderJob::SetPriority(net::RequestPriority priority, int32_t intra_priority_value) {} void AppCacheURLLoaderJob::PauseReadingBodyFromNet() {} void AppCacheURLLoaderJob::ResumeReadingBodyFromNet() {} void AppCacheURLLoaderJob::DeleteIfNeeded() { if (binding_.is_bound() || is_deleting_soon_) return; delete this; } void AppCacheURLLoaderJob::Start(network::mojom::URLLoaderRequest request, network::mojom::URLLoaderClientPtr client) { DCHECK(!binding_.is_bound()); binding_.Bind(std::move(request)); client_ = std::move(client); binding_.set_connection_error_handler(base::BindOnce( &AppCacheURLLoaderJob::OnConnectionError, GetDerivedWeakPtr())); } AppCacheURLLoaderJob::AppCacheURLLoaderJob( AppCacheURLLoaderRequest* appcache_request, AppCacheStorage* storage, NavigationLoaderInterceptor::LoaderCallback loader_callback) : storage_(storage->GetWeakPtr()), start_time_tick_(base::TimeTicks::Now()), cache_id_(kAppCacheNoCacheId), is_fallback_(false), binding_(this), writable_handle_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, base::SequencedTaskRunnerHandle::Get()), loader_callback_(std::move(loader_callback)), appcache_request_(appcache_request->GetWeakPtr()), is_main_resource_load_(IsResourceTypeFrame(static_cast( appcache_request->GetResourceRequest()->resource_type))), weak_factory_(this) {} void AppCacheURLLoaderJob::CallLoaderCallback() { DCHECK(loader_callback_); DCHECK(!binding_.is_bound()); std::move(loader_callback_) .Run(base::BindOnce(&AppCacheURLLoaderJob::Start, GetDerivedWeakPtr())); DCHECK(binding_.is_bound()); } void AppCacheURLLoaderJob::OnResponseInfoLoaded( AppCacheResponseInfo* response_info, int64_t response_id) { DCHECK(IsDeliveringAppCacheResponse()); if (!storage_.get()) { DeliverErrorResponse(); return; } if (response_info) { if (loader_callback_) CallLoaderCallback(); info_ = response_info; reader_.reset( storage_->CreateResponseReader(manifest_url_, entry_.response_id())); if (is_range_request()) SetupRangeResponse(); response_body_stream_ = std::move(data_pipe_.producer_handle); // TODO(ananta) // Move the asynchronous reading and mojo pipe handling code to a helper // class. That would also need a change to BlobURLLoader. // Wait for the data pipe to be ready to accept data. writable_handle_watcher_.Watch( response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, base::Bind(&AppCacheURLLoaderJob::OnResponseBodyStreamReady, GetDerivedWeakPtr())); SendResponseInfo(); ReadMore(); return; } // We failed to load the response headers from the disk cache. if (storage_->service()->storage() == storage_.get()) { // A resource that is expected to be in the appcache is missing. // If the 'check' fails, the corrupt appcache will be deleted. // See http://code.google.com/p/chromium/issues/detail?id=50657 storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, entry_.response_id()); AppCacheHistograms::CountResponseRetrieval( false, is_main_resource_load_, url::Origin::Create(manifest_url_)); } cache_entry_not_found_ = true; // We fallback to the network unless this job was falling back to the // appcache from the network which had already failed in some way. if (!is_fallback_) DeliverNetworkResponse(); else DeliverErrorResponse(); } void AppCacheURLLoaderJob::OnReadComplete(int result) { if (result > 0) { uint32_t bytes_written = static_cast(result); response_body_stream_ = pending_write_->Complete(bytes_written); pending_write_ = nullptr; ReadMore(); return; } writable_handle_watcher_.Cancel(); pending_write_->Complete(0); pending_write_ = nullptr; NotifyCompleted(result); } void AppCacheURLLoaderJob::OnResponseBodyStreamReady(MojoResult result) { // TODO(ananta) // Add proper error handling here. if (result != MOJO_RESULT_OK) { DCHECK(false); NotifyCompleted(net::ERR_FAILED); return; } ReadMore(); } void AppCacheURLLoaderJob::OnConnectionError() { if (storage_.get()) storage_->CancelDelegateCallbacks(this); weak_factory_.InvalidateWeakPtrs(); is_deleting_soon_ = true; base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); } void AppCacheURLLoaderJob::SendResponseInfo() { DCHECK(client_); // If this is null it means the response information was sent to the client. if (!data_pipe_.consumer_handle.is_valid()) return; const net::HttpResponseInfo* http_info = is_range_request() ? range_response_info_.get() : info_->http_response_info(); network::ResourceResponseHead response_head; response_head.headers = http_info->headers; response_head.appcache_id = cache_id_; response_head.appcache_manifest_url = manifest_url_; http_info->headers->GetMimeType(&response_head.mime_type); http_info->headers->GetCharset(&response_head.charset); // TODO(ananta) // Verify if the times sent here are correct. response_head.request_time = http_info->request_time; response_head.response_time = http_info->response_time; response_head.content_length = is_range_request() ? range_response_info_->headers->GetContentLength() : info_->response_data_size(); response_head.connection_info = http_info->connection_info; response_head.socket_address = http_info->socket_address; response_head.was_fetched_via_spdy = http_info->was_fetched_via_spdy; response_head.was_alpn_negotiated = http_info->was_alpn_negotiated; response_head.alpn_negotiated_protocol = http_info->alpn_negotiated_protocol; if (http_info->ssl_info.cert) response_head.ssl_info = http_info->ssl_info; response_head.load_timing = load_timing_info_; client_->OnReceiveResponse(response_head, network::mojom::DownloadedTempFilePtr()); client_->OnStartLoadingResponseBody(std::move(data_pipe_.consumer_handle)); } void AppCacheURLLoaderJob::ReadMore() { DCHECK(!pending_write_.get()); uint32_t num_bytes; // TODO: we should use the abstractions in MojoAsyncResourceHandler. MojoResult result = network::NetToMojoPendingBuffer::BeginWrite( &response_body_stream_, &pending_write_, &num_bytes); if (result == MOJO_RESULT_SHOULD_WAIT) { // The pipe is full. We need to wait for it to have more space. writable_handle_watcher_.ArmOrNotify(); return; } else if (result != MOJO_RESULT_OK) { NotifyCompleted(net::ERR_FAILED); writable_handle_watcher_.Cancel(); response_body_stream_.reset(); return; } CHECK_GT(static_cast(std::numeric_limits::max()), num_bytes); auto buffer = base::MakeRefCounted(pending_write_.get()); uint32_t bytes_to_read = std::min(num_bytes, info_->response_data_size()); reader_->ReadData(buffer.get(), bytes_to_read, base::BindOnce(&AppCacheURLLoaderJob::OnReadComplete, GetDerivedWeakPtr())); } void AppCacheURLLoaderJob::NotifyCompleted(int error_code) { if (storage_.get()) storage_->CancelDelegateCallbacks(this); if (AppCacheRequestHandler::IsRunningInTests()) return; network::URLLoaderCompletionStatus status(error_code); if (!error_code) { const net::HttpResponseInfo* http_info = is_range_request() ? range_response_info_.get() : (info_ ? info_->http_response_info() : nullptr); status.exists_in_cache = http_info->was_cached; status.completion_time = base::TimeTicks::Now(); status.encoded_body_length = is_range_request() ? range_response_info_->headers->GetContentLength() : (info_ ? info_->response_data_size() : 0); status.decoded_body_length = status.encoded_body_length; } client_->OnComplete(status); if (delivery_type_ == APPCACHED_DELIVERY) { AppCacheHistograms::CountResponseRetrieval( error_code == 0, is_main_resource_load_, url::Origin::Create(manifest_url_)); } } } // namespace content