1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/appcache/appcache_request_handler.h"
7 #include "content/browser/appcache/appcache.h"
8 #include "content/browser/appcache/appcache_backend_impl.h"
9 #include "content/browser/appcache/appcache_policy.h"
10 #include "content/browser/appcache/appcache_url_request_job.h"
11 #include "content/browser/service_worker/service_worker_request_handler.h"
12 #include "net/url_request/url_request.h"
13 #include "net/url_request/url_request_job.h"
17 AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost
* host
,
18 ResourceType resource_type
)
20 resource_type_(resource_type
),
21 is_waiting_for_cache_selection_(false),
24 found_network_namespace_(false),
25 cache_entry_not_found_(false),
26 maybe_load_resource_executed_(false) {
28 host_
->AddObserver(this);
31 AppCacheRequestHandler::~AppCacheRequestHandler() {
33 storage()->CancelDelegateCallbacks(this);
34 host_
->RemoveObserver(this);
38 AppCacheStorage
* AppCacheRequestHandler::storage() const {
40 return host_
->storage();
43 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadResource(
44 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
45 maybe_load_resource_executed_
= true;
46 if (!host_
|| !IsSchemeAndMethodSupportedForAppCache(request
) ||
47 cache_entry_not_found_
)
50 // This method can get called multiple times over the life
51 // of a request. The case we detect here is having scheduled
52 // delivery of a "network response" using a job setup on an
53 // earlier call thru this method. To send the request thru
54 // to the network involves restarting the request altogether,
55 // which will call thru to our interception layer again.
56 // This time thru, we return NULL so the request hits the wire.
58 DCHECK(job_
->is_delivering_network_response() ||
59 job_
->cache_entry_not_found());
60 if (job_
->cache_entry_not_found())
61 cache_entry_not_found_
= true;
63 storage()->CancelDelegateCallbacks(this);
67 // Clear out our 'found' fields since we're starting a request for a
68 // new resource, any values in those fields are no longer valid.
69 found_entry_
= AppCacheEntry();
70 found_fallback_entry_
= AppCacheEntry();
71 found_cache_id_
= kAppCacheNoCacheId
;
72 found_manifest_url_
= GURL();
73 found_network_namespace_
= false;
75 if (is_main_resource())
76 MaybeLoadMainResource(request
, network_delegate
);
78 MaybeLoadSubResource(request
, network_delegate
);
80 // If its been setup to deliver a network response, we can just delete
81 // it now and return NULL instead to achieve that since it couldn't
82 // have been started yet.
83 if (job_
.get() && job_
->is_delivering_network_response()) {
84 DCHECK(!job_
->has_been_started());
91 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
92 net::URLRequest
* request
,
93 net::NetworkDelegate
* network_delegate
,
94 const GURL
& location
) {
95 if (!host_
|| !IsSchemeAndMethodSupportedForAppCache(request
) ||
96 cache_entry_not_found_
)
98 if (is_main_resource())
100 // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
101 // it once a more general solution to crbug/121325 is in place.
102 if (!maybe_load_resource_executed_
)
104 if (request
->url().GetOrigin() == location
.GetOrigin())
107 DCHECK(!job_
.get()); // our jobs never generate redirects
109 if (found_fallback_entry_
.has_response_id()) {
110 // 6.9.6, step 4: If this results in a redirect to another origin,
111 // get the resource of the fallback entry.
112 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
113 storage(), host_
, is_main_resource());
114 DeliverAppCachedResponse(
115 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
116 found_manifest_url_
, true, found_namespace_entry_url_
);
117 } else if (!found_network_namespace_
) {
118 // 6.9.6, step 6: Fail the resource load.
119 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
120 storage(), host_
, is_main_resource());
121 DeliverErrorResponse();
123 // 6.9.6 step 3 and 5: Fetch the resource normally.
129 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
130 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
131 if (!host_
|| !IsSchemeAndMethodSupportedForAppCache(request
) ||
132 cache_entry_not_found_
)
134 if (!found_fallback_entry_
.has_response_id())
137 if (request
->status().status() == net::URLRequestStatus::CANCELED
) {
138 // 6.9.6, step 4: But not if the user canceled the download.
142 // We don't fallback for responses that we delivered.
144 DCHECK(!job_
->is_delivering_network_response());
148 if (request
->status().is_success()) {
149 int code_major
= request
->GetResponseCode() / 100;
150 if (code_major
!=4 && code_major
!= 5)
153 // Servers can override the fallback behavior with a response header.
154 const std::string
kFallbackOverrideHeader(
155 "x-chromium-appcache-fallback-override");
156 const std::string
kFallbackOverrideValue(
157 "disallow-fallback");
158 std::string header_value
;
159 request
->GetResponseHeaderByName(kFallbackOverrideHeader
, &header_value
);
160 if (header_value
== kFallbackOverrideValue
)
164 // 6.9.6, step 4: If this results in a 4xx or 5xx status code
165 // or there were network errors, get the resource of the fallback entry.
166 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
167 storage(), host_
, is_main_resource());
168 DeliverAppCachedResponse(
169 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
170 found_manifest_url_
, true, found_namespace_entry_url_
);
174 void AppCacheRequestHandler::GetExtraResponseInfo(
175 int64
* cache_id
, GURL
* manifest_url
) {
176 if (job_
.get() && job_
->is_delivering_appcache_response()) {
177 *cache_id
= job_
->cache_id();
178 *manifest_url
= job_
->manifest_url();
182 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id
) {
185 AppCacheBackendImpl
* backend
= host_
->service()->GetBackend(old_process_id
);
186 host_for_cross_site_transfer_
= backend
->TransferHostOut(host_
->host_id());
187 DCHECK_EQ(host_
, host_for_cross_site_transfer_
.get());
190 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
191 int new_process_id
, int new_host_id
) {
192 if (!host_for_cross_site_transfer_
.get())
194 DCHECK_EQ(host_
, host_for_cross_site_transfer_
.get());
195 AppCacheBackendImpl
* backend
= host_
->service()->GetBackend(new_process_id
);
196 backend
->TransferHostIn(new_host_id
, host_for_cross_site_transfer_
.Pass());
199 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost
* host
) {
200 storage()->CancelDelegateCallbacks(this);
201 host_
= NULL
; // no need to RemoveObserver, the host is being deleted
203 // Since the host is being deleted, we don't have to complete any job
204 // that is current running. It's destined for the bit bucket anyway.
211 void AppCacheRequestHandler::DeliverAppCachedResponse(
212 const AppCacheEntry
& entry
, int64 cache_id
, int64 group_id
,
213 const GURL
& manifest_url
, bool is_fallback
,
214 const GURL
& namespace_entry_url
) {
215 DCHECK(host_
&& job_
.get() && job_
->is_waiting());
216 DCHECK(entry
.has_response_id());
218 if (IsResourceTypeFrame(resource_type_
) && !namespace_entry_url
.is_empty())
219 host_
->NotifyMainResourceIsNamespaceEntry(namespace_entry_url
);
221 job_
->DeliverAppCachedResponse(manifest_url
, group_id
, cache_id
,
225 void AppCacheRequestHandler::DeliverErrorResponse() {
226 DCHECK(job_
.get() && job_
->is_waiting());
227 job_
->DeliverErrorResponse();
230 void AppCacheRequestHandler::DeliverNetworkResponse() {
231 DCHECK(job_
.get() && job_
->is_waiting());
232 job_
->DeliverNetworkResponse();
235 // Main-resource handling ----------------------------------------------
237 void AppCacheRequestHandler::MaybeLoadMainResource(
238 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
242 // If a page falls into the scope of a ServiceWorker, any matching AppCaches
243 // should be ignored. This depends on the ServiceWorker handler being invoked
244 // prior to the AppCache handler.
245 if (ServiceWorkerRequestHandler::IsControlledByServiceWorker(request
)) {
246 host_
->enable_cache_selection(false);
250 host_
->enable_cache_selection(true);
252 const AppCacheHost
* spawning_host
=
253 (resource_type_
== RESOURCE_TYPE_SHARED_WORKER
) ?
254 host_
: host_
->GetSpawningHost();
255 GURL preferred_manifest_url
= spawning_host
?
256 spawning_host
->preferred_manifest_url() : GURL();
258 // We may have to wait for our storage query to complete, but
259 // this query can also complete syncrhonously.
260 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
261 storage(), host_
, is_main_resource());
262 storage()->FindResponseForMainRequest(
263 request
->url(), preferred_manifest_url
, this);
266 void AppCacheRequestHandler::OnMainResponseFound(
267 const GURL
& url
, const AppCacheEntry
& entry
,
268 const GURL
& namespace_entry_url
, const AppCacheEntry
& fallback_entry
,
269 int64 cache_id
, int64 group_id
, const GURL
& manifest_url
) {
272 DCHECK(is_main_resource());
273 DCHECK(!entry
.IsForeign());
274 DCHECK(!fallback_entry
.IsForeign());
275 DCHECK(!(entry
.has_response_id() && fallback_entry
.has_response_id()));
280 AppCachePolicy
* policy
= host_
->service()->appcache_policy();
281 bool was_blocked_by_policy
= !manifest_url
.is_empty() && policy
&&
282 !policy
->CanLoadAppCache(manifest_url
, host_
->first_party_url());
284 if (was_blocked_by_policy
) {
285 if (IsResourceTypeFrame(resource_type_
)) {
286 host_
->NotifyMainResourceBlocked(manifest_url
);
288 DCHECK_EQ(resource_type_
, RESOURCE_TYPE_SHARED_WORKER
);
289 host_
->frontend()->OnContentBlocked(host_
->host_id(), manifest_url
);
291 DeliverNetworkResponse();
295 if (IsResourceTypeFrame(resource_type_
) && cache_id
!= kAppCacheNoCacheId
) {
296 // AppCacheHost loads and holds a reference to the main resource cache
297 // for two reasons, firstly to preload the cache into the working set
298 // in advance of subresource loads happening, secondly to prevent the
299 // AppCache from falling out of the working set on frame navigations.
300 host_
->LoadMainResourceCache(cache_id
);
301 host_
->set_preferred_manifest_url(manifest_url
);
304 // 6.11.1 Navigating across documents, steps 10 and 14.
306 found_entry_
= entry
;
307 found_namespace_entry_url_
= namespace_entry_url
;
308 found_fallback_entry_
= fallback_entry
;
309 found_cache_id_
= cache_id
;
310 found_group_id_
= group_id
;
311 found_manifest_url_
= manifest_url
;
312 found_network_namespace_
= false; // not applicable to main requests
314 if (found_entry_
.has_response_id()) {
315 DCHECK(!found_fallback_entry_
.has_response_id());
316 DeliverAppCachedResponse(
317 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
318 false, found_namespace_entry_url_
);
320 DeliverNetworkResponse();
324 // Sub-resource handling ----------------------------------------------
326 void AppCacheRequestHandler::MaybeLoadSubResource(
327 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
330 if (host_
->is_selection_pending()) {
331 // We have to wait until cache selection is complete and the
332 // selected cache is loaded.
333 is_waiting_for_cache_selection_
= true;
334 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
335 storage(), host_
, is_main_resource());
339 if (!host_
->associated_cache() ||
340 !host_
->associated_cache()->is_complete() ||
341 host_
->associated_cache()->owning_group()->is_being_deleted()) {
345 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
346 storage(), host_
, is_main_resource());
347 ContinueMaybeLoadSubResource();
350 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
351 // 6.9.6 Changes to the networking model
352 // If the resource is not to be fetched using the HTTP GET mechanism or
353 // equivalent ... then fetch the resource normally.
355 DCHECK(host_
->associated_cache() && host_
->associated_cache()->is_complete());
357 const GURL
& url
= job_
->request()->url();
358 AppCache
* cache
= host_
->associated_cache();
359 storage()->FindResponseForSubRequest(
360 host_
->associated_cache(), url
,
361 &found_entry_
, &found_fallback_entry_
, &found_network_namespace_
);
363 if (found_entry_
.has_response_id()) {
364 // Step 2: If there's an entry, get it instead.
365 DCHECK(!found_network_namespace_
&&
366 !found_fallback_entry_
.has_response_id());
367 found_cache_id_
= cache
->cache_id();
368 found_group_id_
= cache
->owning_group()->group_id();
369 found_manifest_url_
= cache
->owning_group()->manifest_url();
370 DeliverAppCachedResponse(
371 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
376 if (found_fallback_entry_
.has_response_id()) {
377 // Step 4: Fetch the resource normally, if this results
378 // in certain conditions, then use the fallback.
379 DCHECK(!found_network_namespace_
&&
380 !found_entry_
.has_response_id());
381 found_cache_id_
= cache
->cache_id();
382 found_manifest_url_
= cache
->owning_group()->manifest_url();
383 DeliverNetworkResponse();
387 if (found_network_namespace_
) {
388 // Step 3 and 5: Fetch the resource normally.
389 DCHECK(!found_entry_
.has_response_id() &&
390 !found_fallback_entry_
.has_response_id());
391 DeliverNetworkResponse();
395 // Step 6: Fail the resource load.
396 DeliverErrorResponse();
399 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost
* host
) {
400 DCHECK(host
== host_
);
401 if (is_main_resource())
403 if (!is_waiting_for_cache_selection_
)
406 is_waiting_for_cache_selection_
= false;
408 if (!host_
->associated_cache() ||
409 !host_
->associated_cache()->is_complete()) {
410 DeliverNetworkResponse();
414 ContinueMaybeLoadSubResource();
417 } // namespace content