| // Copyright (c) 2011 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 "webkit/appcache/appcache_request_handler.h" |
| |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_job.h" |
| #include "webkit/appcache/appcache.h" |
| #include "webkit/appcache/appcache_policy.h" |
| #include "webkit/appcache/appcache_url_request_job.h" |
| |
| namespace appcache { |
| |
| AppCacheRequestHandler::AppCacheRequestHandler( |
| AppCacheHost* host, ResourceType::Type resource_type) |
| : host_(host), resource_type_(resource_type), |
| is_waiting_for_cache_selection_(false), found_group_id_(0), |
| found_cache_id_(0), found_network_namespace_(false), |
| cache_entry_not_found_(false), maybe_load_resource_executed_(false) { |
| DCHECK(host_); |
| host_->AddObserver(this); |
| } |
| |
| AppCacheRequestHandler::~AppCacheRequestHandler() { |
| if (host_) { |
| storage()->CancelDelegateCallbacks(this); |
| host_->RemoveObserver(this); |
| } |
| } |
| |
| AppCacheStorage* AppCacheRequestHandler::storage() const { |
| DCHECK(host_); |
| return host_->service()->storage(); |
| } |
| |
| void AppCacheRequestHandler::GetExtraResponseInfo( |
| int64* cache_id, GURL* manifest_url) { |
| if (job_ && job_->is_delivering_appcache_response()) { |
| *cache_id = job_->cache_id(); |
| *manifest_url = job_->manifest_url(); |
| } |
| } |
| |
| AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource( |
| net::URLRequest* request, net::NetworkDelegate* network_delegate) { |
| maybe_load_resource_executed_ = true; |
| if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_) |
| return NULL; |
| |
| // This method can get called multiple times over the life |
| // of a request. The case we detect here is having scheduled |
| // delivery of a "network response" using a job setup on an |
| // earlier call thru this method. To send the request thru |
| // to the network involves restarting the request altogether, |
| // which will call thru to our interception layer again. |
| // This time thru, we return NULL so the request hits the wire. |
| if (job_) { |
| DCHECK(job_->is_delivering_network_response() || |
| job_->cache_entry_not_found()); |
| if (job_->cache_entry_not_found()) |
| cache_entry_not_found_ = true; |
| job_ = NULL; |
| storage()->CancelDelegateCallbacks(this); |
| return NULL; |
| } |
| |
| // Clear out our 'found' fields since we're starting a request for a |
| // new resource, any values in those fields are no longer valid. |
| found_entry_ = AppCacheEntry(); |
| found_fallback_entry_ = AppCacheEntry(); |
| found_cache_id_ = kNoCacheId; |
| found_manifest_url_ = GURL(); |
| found_network_namespace_ = false; |
| |
| if (is_main_resource()) |
| MaybeLoadMainResource(request, network_delegate); |
| else |
| MaybeLoadSubResource(request, network_delegate); |
| |
| // If its been setup to deliver a network response, we can just delete |
| // it now and return NULL instead to achieve that since it couldn't |
| // have been started yet. |
| if (job_ && job_->is_delivering_network_response()) { |
| DCHECK(!job_->has_been_started()); |
| job_ = NULL; |
| } |
| |
| return job_; |
| } |
| |
| AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate, |
| const GURL& location) { |
| if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_) |
| return NULL; |
| if (is_main_resource()) |
| return NULL; |
| // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of |
| // it once a more general solution to crbug/121325 is in place. |
| if (!maybe_load_resource_executed_) |
| return NULL; |
| if (request->url().GetOrigin() == location.GetOrigin()) |
| return NULL; |
| |
| DCHECK(!job_); // our jobs never generate redirects |
| |
| if (found_fallback_entry_.has_response_id()) { |
| // 6.9.6, step 4: If this results in a redirect to another origin, |
| // get the resource of the fallback entry. |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| DeliverAppCachedResponse( |
| found_fallback_entry_, found_cache_id_, found_group_id_, |
| found_manifest_url_, true, found_namespace_entry_url_); |
| } else if (!found_network_namespace_) { |
| // 6.9.6, step 6: Fail the resource load. |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| DeliverErrorResponse(); |
| } else { |
| // 6.9.6 step 3 and 5: Fetch the resource normally. |
| } |
| |
| return job_; |
| } |
| |
| AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( |
| net::URLRequest* request, net::NetworkDelegate* network_delegate) { |
| if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_) |
| return NULL; |
| if (!found_fallback_entry_.has_response_id()) |
| return NULL; |
| |
| if (request->status().status() == net::URLRequestStatus::CANCELED) { |
| // 6.9.6, step 4: But not if the user canceled the download. |
| return NULL; |
| } |
| |
| // We don't fallback for responses that we delivered. |
| if (job_) { |
| DCHECK(!job_->is_delivering_network_response()); |
| return NULL; |
| } |
| |
| if (request->status().is_success()) { |
| int code_major = request->GetResponseCode() / 100; |
| if (code_major !=4 && code_major != 5) |
| return NULL; |
| |
| // Servers can override the fallback behavior with a response header. |
| const std::string kFallbackOverrideHeader( |
| "x-chromium-appcache-fallback-override"); |
| const std::string kFallbackOverrideValue( |
| "disallow-fallback"); |
| std::string header_value; |
| request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value); |
| if (header_value == kFallbackOverrideValue) |
| return NULL; |
| } |
| |
| // 6.9.6, step 4: If this results in a 4xx or 5xx status code |
| // or there were network errors, get the resource of the fallback entry. |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| DeliverAppCachedResponse( |
| found_fallback_entry_, found_cache_id_, found_group_id_, |
| found_manifest_url_, true, found_namespace_entry_url_); |
| return job_; |
| } |
| |
| void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) { |
| storage()->CancelDelegateCallbacks(this); |
| host_ = NULL; // no need to RemoveObserver, the host is being deleted |
| |
| // Since the host is being deleted, we don't have to complete any job |
| // that is current running. It's destined for the bit bucket anyway. |
| if (job_) { |
| job_->Kill(); |
| job_ = NULL; |
| } |
| } |
| |
| void AppCacheRequestHandler::DeliverAppCachedResponse( |
| const AppCacheEntry& entry, int64 cache_id, int64 group_id, |
| const GURL& manifest_url, bool is_fallback, |
| const GURL& namespace_entry_url) { |
| DCHECK(host_ && job_ && job_->is_waiting()); |
| DCHECK(entry.has_response_id()); |
| |
| if (ResourceType::IsFrame(resource_type_) && !namespace_entry_url.is_empty()) |
| host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url); |
| |
| job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id, |
| entry, is_fallback); |
| } |
| |
| void AppCacheRequestHandler::DeliverErrorResponse() { |
| DCHECK(job_ && job_->is_waiting()); |
| job_->DeliverErrorResponse(); |
| } |
| |
| void AppCacheRequestHandler::DeliverNetworkResponse() { |
| DCHECK(job_ && job_->is_waiting()); |
| job_->DeliverNetworkResponse(); |
| } |
| |
| // Main-resource handling ---------------------------------------------- |
| |
| void AppCacheRequestHandler::MaybeLoadMainResource( |
| net::URLRequest* request, net::NetworkDelegate* network_delegate) { |
| DCHECK(!job_); |
| DCHECK(host_); |
| |
| const AppCacheHost* spawning_host = |
| ResourceType::IsSharedWorker(resource_type_) ? |
| host_ : host_->GetSpawningHost(); |
| GURL preferred_manifest_url = spawning_host ? |
| spawning_host->preferred_manifest_url() : GURL(); |
| |
| // We may have to wait for our storage query to complete, but |
| // this query can also complete syncrhonously. |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| storage()->FindResponseForMainRequest( |
| request->url(), preferred_manifest_url, this); |
| } |
| |
| void AppCacheRequestHandler::OnMainResponseFound( |
| const GURL& url, const AppCacheEntry& entry, |
| const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, |
| int64 cache_id, int64 group_id, const GURL& manifest_url) { |
| DCHECK(job_); |
| DCHECK(host_); |
| DCHECK(is_main_resource()); |
| DCHECK(!entry.IsForeign()); |
| DCHECK(!fallback_entry.IsForeign()); |
| DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id())); |
| |
| if (!job_) |
| return; |
| |
| AppCachePolicy* policy = host_->service()->appcache_policy(); |
| bool was_blocked_by_policy = !manifest_url.is_empty() && policy && |
| !policy->CanLoadAppCache(manifest_url, host_->first_party_url()); |
| |
| if (was_blocked_by_policy) { |
| if (ResourceType::IsFrame(resource_type_)) { |
| host_->NotifyMainResourceBlocked(manifest_url); |
| } else { |
| DCHECK(ResourceType::IsSharedWorker(resource_type_)); |
| host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url); |
| } |
| DeliverNetworkResponse(); |
| return; |
| } |
| |
| if (ResourceType::IsFrame(resource_type_) && cache_id != kNoCacheId) { |
| // AppCacheHost loads and holds a reference to the main resource cache |
| // for two reasons, firstly to preload the cache into the working set |
| // in advance of subresource loads happening, secondly to prevent the |
| // AppCache from falling out of the working set on frame navigations. |
| host_->LoadMainResourceCache(cache_id); |
| host_->set_preferred_manifest_url(manifest_url); |
| } |
| |
| // 6.11.1 Navigating across documents, steps 10 and 14. |
| |
| found_entry_ = entry; |
| found_namespace_entry_url_ = namespace_entry_url; |
| found_fallback_entry_ = fallback_entry; |
| found_cache_id_ = cache_id; |
| found_group_id_ = group_id; |
| found_manifest_url_ = manifest_url; |
| found_network_namespace_ = false; // not applicable to main requests |
| |
| if (found_entry_.has_response_id()) { |
| DCHECK(!found_fallback_entry_.has_response_id()); |
| DeliverAppCachedResponse( |
| found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, |
| false, found_namespace_entry_url_); |
| } else { |
| DeliverNetworkResponse(); |
| } |
| } |
| |
| // Sub-resource handling ---------------------------------------------- |
| |
| void AppCacheRequestHandler::MaybeLoadSubResource( |
| net::URLRequest* request, net::NetworkDelegate* network_delegate) { |
| DCHECK(!job_); |
| |
| if (host_->is_selection_pending()) { |
| // We have to wait until cache selection is complete and the |
| // selected cache is loaded. |
| is_waiting_for_cache_selection_ = true; |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| return; |
| } |
| |
| if (!host_->associated_cache() || |
| !host_->associated_cache()->is_complete()) { |
| return; |
| } |
| |
| job_ = new AppCacheURLRequestJob(request, network_delegate, storage()); |
| ContinueMaybeLoadSubResource(); |
| } |
| |
| void AppCacheRequestHandler::ContinueMaybeLoadSubResource() { |
| // 6.9.6 Changes to the networking model |
| // If the resource is not to be fetched using the HTTP GET mechanism or |
| // equivalent ... then fetch the resource normally. |
| DCHECK(job_); |
| DCHECK(host_->associated_cache() && |
| host_->associated_cache()->is_complete()); |
| |
| const GURL& url = job_->request()->url(); |
| AppCache* cache = host_->associated_cache(); |
| storage()->FindResponseForSubRequest( |
| host_->associated_cache(), url, |
| &found_entry_, &found_fallback_entry_, &found_network_namespace_); |
| |
| if (found_entry_.has_response_id()) { |
| // Step 2: If there's an entry, get it instead. |
| DCHECK(!found_network_namespace_ && |
| !found_fallback_entry_.has_response_id()); |
| found_cache_id_ = cache->cache_id(); |
| found_group_id_ = cache->owning_group()->group_id(); |
| found_manifest_url_ = cache->owning_group()->manifest_url(); |
| DeliverAppCachedResponse( |
| found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, |
| false, GURL()); |
| return; |
| } |
| |
| if (found_fallback_entry_.has_response_id()) { |
| // Step 4: Fetch the resource normally, if this results |
| // in certain conditions, then use the fallback. |
| DCHECK(!found_network_namespace_ && |
| !found_entry_.has_response_id()); |
| found_cache_id_ = cache->cache_id(); |
| found_manifest_url_ = cache->owning_group()->manifest_url(); |
| DeliverNetworkResponse(); |
| return; |
| } |
| |
| if (found_network_namespace_) { |
| // Step 3 and 5: Fetch the resource normally. |
| DCHECK(!found_entry_.has_response_id() && |
| !found_fallback_entry_.has_response_id()); |
| DeliverNetworkResponse(); |
| return; |
| } |
| |
| // Step 6: Fail the resource load. |
| DeliverErrorResponse(); |
| } |
| |
| void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) { |
| DCHECK(host == host_); |
| if (is_main_resource()) |
| return; |
| if (!is_waiting_for_cache_selection_) |
| return; |
| |
| is_waiting_for_cache_selection_ = false; |
| |
| if (!host_->associated_cache() || |
| !host_->associated_cache()->is_complete()) { |
| DeliverNetworkResponse(); |
| return; |
| } |
| |
| ContinueMaybeLoadSubResource(); |
| } |
| |
| } // namespace appcache |