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 "webkit/browser/appcache/appcache_request_handler.h"
7 #include "net/url_request/url_request.h"
8 #include "net/url_request/url_request_job.h"
9 #include "webkit/browser/appcache/appcache.h"
10 #include "webkit/browser/appcache/appcache_backend_impl.h"
11 #include "webkit/browser/appcache/appcache_policy.h"
12 #include "webkit/browser/appcache/appcache_url_request_job.h"
16 AppCacheRequestHandler::AppCacheRequestHandler(
17 AppCacheHost
* host
, ResourceType::Type resource_type
)
18 : host_(host
), resource_type_(resource_type
),
19 is_waiting_for_cache_selection_(false), found_group_id_(0),
20 found_cache_id_(0), found_network_namespace_(false),
21 cache_entry_not_found_(false), maybe_load_resource_executed_(false) {
23 host_
->AddObserver(this);
26 AppCacheRequestHandler::~AppCacheRequestHandler() {
28 storage()->CancelDelegateCallbacks(this);
29 host_
->RemoveObserver(this);
33 AppCacheStorage
* AppCacheRequestHandler::storage() const {
35 return host_
->storage();
38 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadResource(
39 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
40 maybe_load_resource_executed_
= true;
41 if (!host_
|| !IsSchemeAndMethodSupported(request
) || cache_entry_not_found_
)
44 // This method can get called multiple times over the life
45 // of a request. The case we detect here is having scheduled
46 // delivery of a "network response" using a job setup on an
47 // earlier call thru this method. To send the request thru
48 // to the network involves restarting the request altogether,
49 // which will call thru to our interception layer again.
50 // This time thru, we return NULL so the request hits the wire.
52 DCHECK(job_
->is_delivering_network_response() ||
53 job_
->cache_entry_not_found());
54 if (job_
->cache_entry_not_found())
55 cache_entry_not_found_
= true;
57 storage()->CancelDelegateCallbacks(this);
61 // Clear out our 'found' fields since we're starting a request for a
62 // new resource, any values in those fields are no longer valid.
63 found_entry_
= AppCacheEntry();
64 found_fallback_entry_
= AppCacheEntry();
65 found_cache_id_
= kAppCacheNoCacheId
;
66 found_manifest_url_
= GURL();
67 found_network_namespace_
= false;
69 if (is_main_resource())
70 MaybeLoadMainResource(request
, network_delegate
);
72 MaybeLoadSubResource(request
, network_delegate
);
74 // If its been setup to deliver a network response, we can just delete
75 // it now and return NULL instead to achieve that since it couldn't
76 // have been started yet.
77 if (job_
.get() && job_
->is_delivering_network_response()) {
78 DCHECK(!job_
->has_been_started());
85 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
86 net::URLRequest
* request
,
87 net::NetworkDelegate
* network_delegate
,
88 const GURL
& location
) {
89 if (!host_
|| !IsSchemeAndMethodSupported(request
) || cache_entry_not_found_
)
91 if (is_main_resource())
93 // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
94 // it once a more general solution to crbug/121325 is in place.
95 if (!maybe_load_resource_executed_
)
97 if (request
->url().GetOrigin() == location
.GetOrigin())
100 DCHECK(!job_
.get()); // our jobs never generate redirects
102 if (found_fallback_entry_
.has_response_id()) {
103 // 6.9.6, step 4: If this results in a redirect to another origin,
104 // get the resource of the fallback entry.
105 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
106 storage(), host_
, is_main_resource());
107 DeliverAppCachedResponse(
108 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
109 found_manifest_url_
, true, found_namespace_entry_url_
);
110 } else if (!found_network_namespace_
) {
111 // 6.9.6, step 6: Fail the resource load.
112 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
113 storage(), host_
, is_main_resource());
114 DeliverErrorResponse();
116 // 6.9.6 step 3 and 5: Fetch the resource normally.
122 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
123 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
124 if (!host_
|| !IsSchemeAndMethodSupported(request
) || cache_entry_not_found_
)
126 if (!found_fallback_entry_
.has_response_id())
129 if (request
->status().status() == net::URLRequestStatus::CANCELED
) {
130 // 6.9.6, step 4: But not if the user canceled the download.
134 // We don't fallback for responses that we delivered.
136 DCHECK(!job_
->is_delivering_network_response());
140 if (request
->status().is_success()) {
141 int code_major
= request
->GetResponseCode() / 100;
142 if (code_major
!=4 && code_major
!= 5)
145 // Servers can override the fallback behavior with a response header.
146 const std::string
kFallbackOverrideHeader(
147 "x-chromium-appcache-fallback-override");
148 const std::string
kFallbackOverrideValue(
149 "disallow-fallback");
150 std::string header_value
;
151 request
->GetResponseHeaderByName(kFallbackOverrideHeader
, &header_value
);
152 if (header_value
== kFallbackOverrideValue
)
156 // 6.9.6, step 4: If this results in a 4xx or 5xx status code
157 // or there were network errors, get the resource of the fallback entry.
158 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
159 storage(), host_
, is_main_resource());
160 DeliverAppCachedResponse(
161 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
162 found_manifest_url_
, true, found_namespace_entry_url_
);
166 void AppCacheRequestHandler::GetExtraResponseInfo(
167 int64
* cache_id
, GURL
* manifest_url
) {
168 if (job_
.get() && job_
->is_delivering_appcache_response()) {
169 *cache_id
= job_
->cache_id();
170 *manifest_url
= job_
->manifest_url();
174 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id
) {
177 AppCacheBackendImpl
* backend
= host_
->service()->GetBackend(old_process_id
);
178 host_for_cross_site_transfer_
= backend
->TransferHostOut(host_
->host_id());
179 DCHECK_EQ(host_
, host_for_cross_site_transfer_
.get());
182 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
183 int new_process_id
, int new_host_id
) {
184 if (!host_for_cross_site_transfer_
.get())
186 DCHECK_EQ(host_
, host_for_cross_site_transfer_
.get());
187 AppCacheBackendImpl
* backend
= host_
->service()->GetBackend(new_process_id
);
188 backend
->TransferHostIn(new_host_id
, host_for_cross_site_transfer_
.Pass());
191 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost
* host
) {
192 storage()->CancelDelegateCallbacks(this);
193 host_
= NULL
; // no need to RemoveObserver, the host is being deleted
195 // Since the host is being deleted, we don't have to complete any job
196 // that is current running. It's destined for the bit bucket anyway.
203 void AppCacheRequestHandler::DeliverAppCachedResponse(
204 const AppCacheEntry
& entry
, int64 cache_id
, int64 group_id
,
205 const GURL
& manifest_url
, bool is_fallback
,
206 const GURL
& namespace_entry_url
) {
207 DCHECK(host_
&& job_
.get() && job_
->is_waiting());
208 DCHECK(entry
.has_response_id());
210 if (ResourceType::IsFrame(resource_type_
) && !namespace_entry_url
.is_empty())
211 host_
->NotifyMainResourceIsNamespaceEntry(namespace_entry_url
);
213 job_
->DeliverAppCachedResponse(manifest_url
, group_id
, cache_id
,
217 void AppCacheRequestHandler::DeliverErrorResponse() {
218 DCHECK(job_
.get() && job_
->is_waiting());
219 job_
->DeliverErrorResponse();
222 void AppCacheRequestHandler::DeliverNetworkResponse() {
223 DCHECK(job_
.get() && job_
->is_waiting());
224 job_
->DeliverNetworkResponse();
227 // Main-resource handling ----------------------------------------------
229 void AppCacheRequestHandler::MaybeLoadMainResource(
230 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
234 const AppCacheHost
* spawning_host
=
235 ResourceType::IsSharedWorker(resource_type_
) ?
236 host_
: host_
->GetSpawningHost();
237 GURL preferred_manifest_url
= spawning_host
?
238 spawning_host
->preferred_manifest_url() : GURL();
240 // We may have to wait for our storage query to complete, but
241 // this query can also complete syncrhonously.
242 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
243 storage(), host_
, is_main_resource());
244 storage()->FindResponseForMainRequest(
245 request
->url(), preferred_manifest_url
, this);
248 void AppCacheRequestHandler::OnMainResponseFound(
249 const GURL
& url
, const AppCacheEntry
& entry
,
250 const GURL
& namespace_entry_url
, const AppCacheEntry
& fallback_entry
,
251 int64 cache_id
, int64 group_id
, const GURL
& manifest_url
) {
254 DCHECK(is_main_resource());
255 DCHECK(!entry
.IsForeign());
256 DCHECK(!fallback_entry
.IsForeign());
257 DCHECK(!(entry
.has_response_id() && fallback_entry
.has_response_id()));
262 AppCachePolicy
* policy
= host_
->service()->appcache_policy();
263 bool was_blocked_by_policy
= !manifest_url
.is_empty() && policy
&&
264 !policy
->CanLoadAppCache(manifest_url
, host_
->first_party_url());
266 if (was_blocked_by_policy
) {
267 if (ResourceType::IsFrame(resource_type_
)) {
268 host_
->NotifyMainResourceBlocked(manifest_url
);
270 DCHECK(ResourceType::IsSharedWorker(resource_type_
));
271 host_
->frontend()->OnContentBlocked(host_
->host_id(), manifest_url
);
273 DeliverNetworkResponse();
277 if (ResourceType::IsFrame(resource_type_
) && cache_id
!= kAppCacheNoCacheId
) {
278 // AppCacheHost loads and holds a reference to the main resource cache
279 // for two reasons, firstly to preload the cache into the working set
280 // in advance of subresource loads happening, secondly to prevent the
281 // AppCache from falling out of the working set on frame navigations.
282 host_
->LoadMainResourceCache(cache_id
);
283 host_
->set_preferred_manifest_url(manifest_url
);
286 // 6.11.1 Navigating across documents, steps 10 and 14.
288 found_entry_
= entry
;
289 found_namespace_entry_url_
= namespace_entry_url
;
290 found_fallback_entry_
= fallback_entry
;
291 found_cache_id_
= cache_id
;
292 found_group_id_
= group_id
;
293 found_manifest_url_
= manifest_url
;
294 found_network_namespace_
= false; // not applicable to main requests
296 if (found_entry_
.has_response_id()) {
297 DCHECK(!found_fallback_entry_
.has_response_id());
298 DeliverAppCachedResponse(
299 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
300 false, found_namespace_entry_url_
);
302 DeliverNetworkResponse();
306 // Sub-resource handling ----------------------------------------------
308 void AppCacheRequestHandler::MaybeLoadSubResource(
309 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
312 if (host_
->is_selection_pending()) {
313 // We have to wait until cache selection is complete and the
314 // selected cache is loaded.
315 is_waiting_for_cache_selection_
= true;
316 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
317 storage(), host_
, is_main_resource());
321 if (!host_
->associated_cache() ||
322 !host_
->associated_cache()->is_complete() ||
323 host_
->associated_cache()->owning_group()->is_being_deleted()) {
327 job_
= new AppCacheURLRequestJob(request
, network_delegate
,
328 storage(), host_
, is_main_resource());
329 ContinueMaybeLoadSubResource();
332 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
333 // 6.9.6 Changes to the networking model
334 // If the resource is not to be fetched using the HTTP GET mechanism or
335 // equivalent ... then fetch the resource normally.
337 DCHECK(host_
->associated_cache() && host_
->associated_cache()->is_complete());
339 const GURL
& url
= job_
->request()->url();
340 AppCache
* cache
= host_
->associated_cache();
341 storage()->FindResponseForSubRequest(
342 host_
->associated_cache(), url
,
343 &found_entry_
, &found_fallback_entry_
, &found_network_namespace_
);
345 if (found_entry_
.has_response_id()) {
346 // Step 2: If there's an entry, get it instead.
347 DCHECK(!found_network_namespace_
&&
348 !found_fallback_entry_
.has_response_id());
349 found_cache_id_
= cache
->cache_id();
350 found_group_id_
= cache
->owning_group()->group_id();
351 found_manifest_url_
= cache
->owning_group()->manifest_url();
352 DeliverAppCachedResponse(
353 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
358 if (found_fallback_entry_
.has_response_id()) {
359 // Step 4: Fetch the resource normally, if this results
360 // in certain conditions, then use the fallback.
361 DCHECK(!found_network_namespace_
&&
362 !found_entry_
.has_response_id());
363 found_cache_id_
= cache
->cache_id();
364 found_manifest_url_
= cache
->owning_group()->manifest_url();
365 DeliverNetworkResponse();
369 if (found_network_namespace_
) {
370 // Step 3 and 5: Fetch the resource normally.
371 DCHECK(!found_entry_
.has_response_id() &&
372 !found_fallback_entry_
.has_response_id());
373 DeliverNetworkResponse();
377 // Step 6: Fail the resource load.
378 DeliverErrorResponse();
381 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost
* host
) {
382 DCHECK(host
== host_
);
383 if (is_main_resource())
385 if (!is_waiting_for_cache_selection_
)
388 is_waiting_for_cache_selection_
= false;
390 if (!host_
->associated_cache() ||
391 !host_
->associated_cache()->is_complete()) {
392 DeliverNetworkResponse();
396 ContinueMaybeLoadSubResource();
399 } // namespace appcache