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/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/appcache/appcache.h"
10 #include "webkit/appcache/appcache_policy.h"
11 #include "webkit/appcache/appcache_url_request_job.h"
15 AppCacheRequestHandler::AppCacheRequestHandler(
16 AppCacheHost
* host
, ResourceType::Type resource_type
)
17 : host_(host
), resource_type_(resource_type
),
18 is_waiting_for_cache_selection_(false), found_group_id_(0),
19 found_cache_id_(0), found_network_namespace_(false),
20 cache_entry_not_found_(false), maybe_load_resource_executed_(false) {
22 host_
->AddObserver(this);
25 AppCacheRequestHandler::~AppCacheRequestHandler() {
27 storage()->CancelDelegateCallbacks(this);
28 host_
->RemoveObserver(this);
32 AppCacheStorage
* AppCacheRequestHandler::storage() const {
34 return host_
->service()->storage();
37 void AppCacheRequestHandler::GetExtraResponseInfo(
38 int64
* cache_id
, GURL
* manifest_url
) {
39 if (job_
&& job_
->is_delivering_appcache_response()) {
40 *cache_id
= job_
->cache_id();
41 *manifest_url
= job_
->manifest_url();
45 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadResource(
46 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
47 maybe_load_resource_executed_
= true;
48 if (!host_
|| !IsSchemeAndMethodSupported(request
) || cache_entry_not_found_
)
51 // This method can get called multiple times over the life
52 // of a request. The case we detect here is having scheduled
53 // delivery of a "network response" using a job setup on an
54 // earlier call thru this method. To send the request thru
55 // to the network involves restarting the request altogether,
56 // which will call thru to our interception layer again.
57 // This time thru, we return NULL so the request hits the wire.
59 DCHECK(job_
->is_delivering_network_response() ||
60 job_
->cache_entry_not_found());
61 if (job_
->cache_entry_not_found())
62 cache_entry_not_found_
= true;
64 storage()->CancelDelegateCallbacks(this);
68 // Clear out our 'found' fields since we're starting a request for a
69 // new resource, any values in those fields are no longer valid.
70 found_entry_
= AppCacheEntry();
71 found_fallback_entry_
= AppCacheEntry();
72 found_cache_id_
= kNoCacheId
;
73 found_manifest_url_
= GURL();
74 found_network_namespace_
= false;
76 if (is_main_resource())
77 MaybeLoadMainResource(request
, network_delegate
);
79 MaybeLoadSubResource(request
, network_delegate
);
81 // If its been setup to deliver a network response, we can just delete
82 // it now and return NULL instead to achieve that since it couldn't
83 // have been started yet.
84 if (job_
&& job_
->is_delivering_network_response()) {
85 DCHECK(!job_
->has_been_started());
92 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
93 net::URLRequest
* request
,
94 net::NetworkDelegate
* network_delegate
,
95 const GURL
& location
) {
96 if (!host_
|| !IsSchemeAndMethodSupported(request
) || 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_
); // 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
, storage());
113 DeliverAppCachedResponse(
114 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
115 found_manifest_url_
, true, found_namespace_entry_url_
);
116 } else if (!found_network_namespace_
) {
117 // 6.9.6, step 6: Fail the resource load.
118 job_
= new AppCacheURLRequestJob(request
, network_delegate
, storage());
119 DeliverErrorResponse();
121 // 6.9.6 step 3 and 5: Fetch the resource normally.
127 AppCacheURLRequestJob
* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
128 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
129 if (!host_
|| !IsSchemeAndMethodSupported(request
) || cache_entry_not_found_
)
131 if (!found_fallback_entry_
.has_response_id())
134 if (request
->status().status() == net::URLRequestStatus::CANCELED
) {
135 // 6.9.6, step 4: But not if the user canceled the download.
139 // We don't fallback for responses that we delivered.
141 DCHECK(!job_
->is_delivering_network_response());
145 if (request
->status().is_success()) {
146 int code_major
= request
->GetResponseCode() / 100;
147 if (code_major
!=4 && code_major
!= 5)
150 // Servers can override the fallback behavior with a response header.
151 const std::string
kFallbackOverrideHeader(
152 "x-chromium-appcache-fallback-override");
153 const std::string
kFallbackOverrideValue(
154 "disallow-fallback");
155 std::string header_value
;
156 request
->GetResponseHeaderByName(kFallbackOverrideHeader
, &header_value
);
157 if (header_value
== kFallbackOverrideValue
)
161 // 6.9.6, step 4: If this results in a 4xx or 5xx status code
162 // or there were network errors, get the resource of the fallback entry.
163 job_
= new AppCacheURLRequestJob(request
, network_delegate
, storage());
164 DeliverAppCachedResponse(
165 found_fallback_entry_
, found_cache_id_
, found_group_id_
,
166 found_manifest_url_
, true, found_namespace_entry_url_
);
170 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost
* host
) {
171 storage()->CancelDelegateCallbacks(this);
172 host_
= NULL
; // no need to RemoveObserver, the host is being deleted
174 // Since the host is being deleted, we don't have to complete any job
175 // that is current running. It's destined for the bit bucket anyway.
182 void AppCacheRequestHandler::DeliverAppCachedResponse(
183 const AppCacheEntry
& entry
, int64 cache_id
, int64 group_id
,
184 const GURL
& manifest_url
, bool is_fallback
,
185 const GURL
& namespace_entry_url
) {
186 DCHECK(host_
&& job_
&& job_
->is_waiting());
187 DCHECK(entry
.has_response_id());
189 if (ResourceType::IsFrame(resource_type_
) && !namespace_entry_url
.is_empty())
190 host_
->NotifyMainResourceIsNamespaceEntry(namespace_entry_url
);
192 job_
->DeliverAppCachedResponse(manifest_url
, group_id
, cache_id
,
196 void AppCacheRequestHandler::DeliverErrorResponse() {
197 DCHECK(job_
&& job_
->is_waiting());
198 job_
->DeliverErrorResponse();
201 void AppCacheRequestHandler::DeliverNetworkResponse() {
202 DCHECK(job_
&& job_
->is_waiting());
203 job_
->DeliverNetworkResponse();
206 // Main-resource handling ----------------------------------------------
208 void AppCacheRequestHandler::MaybeLoadMainResource(
209 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
213 const AppCacheHost
* spawning_host
=
214 ResourceType::IsSharedWorker(resource_type_
) ?
215 host_
: host_
->GetSpawningHost();
216 GURL preferred_manifest_url
= spawning_host
?
217 spawning_host
->preferred_manifest_url() : GURL();
219 // We may have to wait for our storage query to complete, but
220 // this query can also complete syncrhonously.
221 job_
= new AppCacheURLRequestJob(request
, network_delegate
, storage());
222 storage()->FindResponseForMainRequest(
223 request
->url(), preferred_manifest_url
, this);
226 void AppCacheRequestHandler::OnMainResponseFound(
227 const GURL
& url
, const AppCacheEntry
& entry
,
228 const GURL
& namespace_entry_url
, const AppCacheEntry
& fallback_entry
,
229 int64 cache_id
, int64 group_id
, const GURL
& manifest_url
) {
232 DCHECK(is_main_resource());
233 DCHECK(!entry
.IsForeign());
234 DCHECK(!fallback_entry
.IsForeign());
235 DCHECK(!(entry
.has_response_id() && fallback_entry
.has_response_id()));
240 AppCachePolicy
* policy
= host_
->service()->appcache_policy();
241 bool was_blocked_by_policy
= !manifest_url
.is_empty() && policy
&&
242 !policy
->CanLoadAppCache(manifest_url
, host_
->first_party_url());
244 if (was_blocked_by_policy
) {
245 if (ResourceType::IsFrame(resource_type_
)) {
246 host_
->NotifyMainResourceBlocked(manifest_url
);
248 DCHECK(ResourceType::IsSharedWorker(resource_type_
));
249 host_
->frontend()->OnContentBlocked(host_
->host_id(), manifest_url
);
251 DeliverNetworkResponse();
255 if (ResourceType::IsFrame(resource_type_
) && cache_id
!= kNoCacheId
) {
256 // AppCacheHost loads and holds a reference to the main resource cache
257 // for two reasons, firstly to preload the cache into the working set
258 // in advance of subresource loads happening, secondly to prevent the
259 // AppCache from falling out of the working set on frame navigations.
260 host_
->LoadMainResourceCache(cache_id
);
261 host_
->set_preferred_manifest_url(manifest_url
);
264 // 6.11.1 Navigating across documents, steps 10 and 14.
266 found_entry_
= entry
;
267 found_namespace_entry_url_
= namespace_entry_url
;
268 found_fallback_entry_
= fallback_entry
;
269 found_cache_id_
= cache_id
;
270 found_group_id_
= group_id
;
271 found_manifest_url_
= manifest_url
;
272 found_network_namespace_
= false; // not applicable to main requests
274 if (found_entry_
.has_response_id()) {
275 DCHECK(!found_fallback_entry_
.has_response_id());
276 DeliverAppCachedResponse(
277 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
278 false, found_namespace_entry_url_
);
280 DeliverNetworkResponse();
284 // Sub-resource handling ----------------------------------------------
286 void AppCacheRequestHandler::MaybeLoadSubResource(
287 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) {
290 if (host_
->is_selection_pending()) {
291 // We have to wait until cache selection is complete and the
292 // selected cache is loaded.
293 is_waiting_for_cache_selection_
= true;
294 job_
= new AppCacheURLRequestJob(request
, network_delegate
, storage());
298 if (!host_
->associated_cache() ||
299 !host_
->associated_cache()->is_complete()) {
303 job_
= new AppCacheURLRequestJob(request
, network_delegate
, storage());
304 ContinueMaybeLoadSubResource();
307 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
308 // 6.9.6 Changes to the networking model
309 // If the resource is not to be fetched using the HTTP GET mechanism or
310 // equivalent ... then fetch the resource normally.
312 DCHECK(host_
->associated_cache() &&
313 host_
->associated_cache()->is_complete());
315 const GURL
& url
= job_
->request()->url();
316 AppCache
* cache
= host_
->associated_cache();
317 storage()->FindResponseForSubRequest(
318 host_
->associated_cache(), url
,
319 &found_entry_
, &found_fallback_entry_
, &found_network_namespace_
);
321 if (found_entry_
.has_response_id()) {
322 // Step 2: If there's an entry, get it instead.
323 DCHECK(!found_network_namespace_
&&
324 !found_fallback_entry_
.has_response_id());
325 found_cache_id_
= cache
->cache_id();
326 found_group_id_
= cache
->owning_group()->group_id();
327 found_manifest_url_
= cache
->owning_group()->manifest_url();
328 DeliverAppCachedResponse(
329 found_entry_
, found_cache_id_
, found_group_id_
, found_manifest_url_
,
334 if (found_fallback_entry_
.has_response_id()) {
335 // Step 4: Fetch the resource normally, if this results
336 // in certain conditions, then use the fallback.
337 DCHECK(!found_network_namespace_
&&
338 !found_entry_
.has_response_id());
339 found_cache_id_
= cache
->cache_id();
340 found_manifest_url_
= cache
->owning_group()->manifest_url();
341 DeliverNetworkResponse();
345 if (found_network_namespace_
) {
346 // Step 3 and 5: Fetch the resource normally.
347 DCHECK(!found_entry_
.has_response_id() &&
348 !found_fallback_entry_
.has_response_id());
349 DeliverNetworkResponse();
353 // Step 6: Fail the resource load.
354 DeliverErrorResponse();
357 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost
* host
) {
358 DCHECK(host
== host_
);
359 if (is_main_resource())
361 if (!is_waiting_for_cache_selection_
)
364 is_waiting_for_cache_selection_
= false;
366 if (!host_
->associated_cache() ||
367 !host_
->associated_cache()->is_complete()) {
368 DeliverNetworkResponse();
372 ContinueMaybeLoadSubResource();
375 } // namespace appcache