Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / appcache / appcache_request_handler.cc
blobbcfbe35cfb041746f3ced16afd9829268a4dadaf
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"
15 namespace content {
17 AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost* host,
18 ResourceType resource_type,
19 bool should_reset_appcache)
20 : host_(host),
21 resource_type_(resource_type),
22 should_reset_appcache_(should_reset_appcache),
23 is_waiting_for_cache_selection_(false),
24 found_group_id_(0),
25 found_cache_id_(0),
26 found_network_namespace_(false),
27 cache_entry_not_found_(false),
28 maybe_load_resource_executed_(false),
29 old_process_id_(0),
30 old_host_id_(kAppCacheNoHostId) {
31 DCHECK(host_);
32 host_->AddObserver(this);
35 AppCacheRequestHandler::~AppCacheRequestHandler() {
36 if (host_) {
37 storage()->CancelDelegateCallbacks(this);
38 host_->RemoveObserver(this);
42 AppCacheStorage* AppCacheRequestHandler::storage() const {
43 DCHECK(host_);
44 return host_->storage();
47 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource(
48 net::URLRequest* request, net::NetworkDelegate* network_delegate) {
49 maybe_load_resource_executed_ = true;
50 if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
51 cache_entry_not_found_)
52 return NULL;
54 // This method can get called multiple times over the life
55 // of a request. The case we detect here is having scheduled
56 // delivery of a "network response" using a job setup on an
57 // earlier call thru this method. To send the request thru
58 // to the network involves restarting the request altogether,
59 // which will call thru to our interception layer again.
60 // This time thru, we return NULL so the request hits the wire.
61 if (job_.get()) {
62 DCHECK(job_->is_delivering_network_response() ||
63 job_->cache_entry_not_found());
64 if (job_->cache_entry_not_found())
65 cache_entry_not_found_ = true;
66 job_ = NULL;
67 storage()->CancelDelegateCallbacks(this);
68 return NULL;
71 // Clear out our 'found' fields since we're starting a request for a
72 // new resource, any values in those fields are no longer valid.
73 found_entry_ = AppCacheEntry();
74 found_fallback_entry_ = AppCacheEntry();
75 found_cache_id_ = kAppCacheNoCacheId;
76 found_manifest_url_ = GURL();
77 found_network_namespace_ = false;
79 if (is_main_resource())
80 MaybeLoadMainResource(request, network_delegate);
81 else
82 MaybeLoadSubResource(request, network_delegate);
84 // If its been setup to deliver a network response, we can just delete
85 // it now and return NULL instead to achieve that since it couldn't
86 // have been started yet.
87 if (job_.get() && job_->is_delivering_network_response()) {
88 DCHECK(!job_->has_been_started());
89 job_ = NULL;
92 return job_.get();
95 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
96 net::URLRequest* request,
97 net::NetworkDelegate* network_delegate,
98 const GURL& location) {
99 if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
100 cache_entry_not_found_)
101 return NULL;
102 if (is_main_resource())
103 return NULL;
104 // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
105 // it once a more general solution to crbug/121325 is in place.
106 if (!maybe_load_resource_executed_)
107 return NULL;
108 if (request->url().GetOrigin() == location.GetOrigin())
109 return NULL;
111 DCHECK(!job_.get()); // our jobs never generate redirects
113 if (found_fallback_entry_.has_response_id()) {
114 // 6.9.6, step 4: If this results in a redirect to another origin,
115 // get the resource of the fallback entry.
116 job_ = new AppCacheURLRequestJob(request, network_delegate,
117 storage(), host_, is_main_resource());
118 DeliverAppCachedResponse(
119 found_fallback_entry_, found_cache_id_, found_group_id_,
120 found_manifest_url_, true, found_namespace_entry_url_);
121 } else if (!found_network_namespace_) {
122 // 6.9.6, step 6: Fail the resource load.
123 job_ = new AppCacheURLRequestJob(request, network_delegate,
124 storage(), host_, is_main_resource());
125 DeliverErrorResponse();
126 } else {
127 // 6.9.6 step 3 and 5: Fetch the resource normally.
130 return job_.get();
133 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
134 net::URLRequest* request, net::NetworkDelegate* network_delegate) {
135 if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
136 cache_entry_not_found_)
137 return NULL;
138 if (!found_fallback_entry_.has_response_id())
139 return NULL;
141 if (request->status().status() == net::URLRequestStatus::CANCELED) {
142 // 6.9.6, step 4: But not if the user canceled the download.
143 return NULL;
146 // We don't fallback for responses that we delivered.
147 if (job_.get()) {
148 DCHECK(!job_->is_delivering_network_response());
149 return NULL;
152 if (request->status().is_success()) {
153 int code_major = request->GetResponseCode() / 100;
154 if (code_major !=4 && code_major != 5)
155 return NULL;
157 // Servers can override the fallback behavior with a response header.
158 const std::string kFallbackOverrideHeader(
159 "x-chromium-appcache-fallback-override");
160 const std::string kFallbackOverrideValue(
161 "disallow-fallback");
162 std::string header_value;
163 request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value);
164 if (header_value == kFallbackOverrideValue)
165 return NULL;
168 // 6.9.6, step 4: If this results in a 4xx or 5xx status code
169 // or there were network errors, get the resource of the fallback entry.
170 job_ = new AppCacheURLRequestJob(request, network_delegate,
171 storage(), host_, is_main_resource());
172 DeliverAppCachedResponse(
173 found_fallback_entry_, found_cache_id_, found_group_id_,
174 found_manifest_url_, true, found_namespace_entry_url_);
175 return job_.get();
178 void AppCacheRequestHandler::GetExtraResponseInfo(
179 int64* cache_id, GURL* manifest_url) {
180 if (job_.get() && job_->is_delivering_appcache_response()) {
181 *cache_id = job_->cache_id();
182 *manifest_url = job_->manifest_url();
186 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) {
187 if (!host_)
188 return;
189 AppCacheBackendImpl* backend = host_->service()->GetBackend(old_process_id);
190 old_process_id_ = old_process_id;
191 old_host_id_ = host_->host_id();
192 host_for_cross_site_transfer_ = backend->TransferHostOut(host_->host_id());
193 DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
196 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
197 int new_process_id, int new_host_id) {
198 if (!host_for_cross_site_transfer_.get())
199 return;
200 DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
201 AppCacheBackendImpl* backend = host_->service()->GetBackend(new_process_id);
202 backend->TransferHostIn(new_host_id, host_for_cross_site_transfer_.Pass());
205 void AppCacheRequestHandler::MaybeCompleteCrossSiteTransferInOldProcess(
206 int old_process_id) {
207 if (!host_ || !host_for_cross_site_transfer_.get() ||
208 old_process_id != old_process_id_) {
209 return;
211 CompleteCrossSiteTransfer(old_process_id_, old_host_id_);
214 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) {
215 storage()->CancelDelegateCallbacks(this);
216 host_ = NULL; // no need to RemoveObserver, the host is being deleted
218 // Since the host is being deleted, we don't have to complete any job
219 // that is current running. It's destined for the bit bucket anyway.
220 if (job_.get()) {
221 job_->Kill();
222 job_ = NULL;
226 void AppCacheRequestHandler::DeliverAppCachedResponse(
227 const AppCacheEntry& entry, int64 cache_id, int64 group_id,
228 const GURL& manifest_url, bool is_fallback,
229 const GURL& namespace_entry_url) {
230 DCHECK(host_ && job_.get() && job_->is_waiting());
231 DCHECK(entry.has_response_id());
233 if (IsResourceTypeFrame(resource_type_) && !namespace_entry_url.is_empty())
234 host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url);
236 job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id,
237 entry, is_fallback);
240 void AppCacheRequestHandler::DeliverErrorResponse() {
241 DCHECK(job_.get() && job_->is_waiting());
242 job_->DeliverErrorResponse();
245 void AppCacheRequestHandler::DeliverNetworkResponse() {
246 DCHECK(job_.get() && job_->is_waiting());
247 job_->DeliverNetworkResponse();
250 // Main-resource handling ----------------------------------------------
252 void AppCacheRequestHandler::MaybeLoadMainResource(
253 net::URLRequest* request, net::NetworkDelegate* network_delegate) {
254 DCHECK(!job_.get());
255 DCHECK(host_);
257 // If a page falls into the scope of a ServiceWorker, any matching AppCaches
258 // should be ignored. This depends on the ServiceWorker handler being invoked
259 // prior to the AppCache handler.
260 if (ServiceWorkerRequestHandler::IsControlledByServiceWorker(request)) {
261 host_->enable_cache_selection(false);
262 return;
265 host_->enable_cache_selection(true);
267 const AppCacheHost* spawning_host =
268 (resource_type_ == RESOURCE_TYPE_SHARED_WORKER) ?
269 host_ : host_->GetSpawningHost();
270 GURL preferred_manifest_url = spawning_host ?
271 spawning_host->preferred_manifest_url() : GURL();
273 // We may have to wait for our storage query to complete, but
274 // this query can also complete syncrhonously.
275 job_ = new AppCacheURLRequestJob(request, network_delegate,
276 storage(), host_, is_main_resource());
277 storage()->FindResponseForMainRequest(
278 request->url(), preferred_manifest_url, this);
281 void AppCacheRequestHandler::OnMainResponseFound(
282 const GURL& url, const AppCacheEntry& entry,
283 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
284 int64 cache_id, int64 group_id, const GURL& manifest_url) {
285 DCHECK(job_.get());
286 DCHECK(host_);
287 DCHECK(is_main_resource());
288 DCHECK(!entry.IsForeign());
289 DCHECK(!fallback_entry.IsForeign());
290 DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id()));
292 if (!job_.get())
293 return;
295 AppCachePolicy* policy = host_->service()->appcache_policy();
296 bool was_blocked_by_policy = !manifest_url.is_empty() && policy &&
297 !policy->CanLoadAppCache(manifest_url, host_->first_party_url());
299 if (was_blocked_by_policy) {
300 if (IsResourceTypeFrame(resource_type_)) {
301 host_->NotifyMainResourceBlocked(manifest_url);
302 } else {
303 DCHECK_EQ(resource_type_, RESOURCE_TYPE_SHARED_WORKER);
304 host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url);
306 DeliverNetworkResponse();
307 return;
310 if (should_reset_appcache_ && !manifest_url.is_empty()) {
311 host_->service()->DeleteAppCacheGroup(
312 manifest_url, net::CompletionCallback());
313 DeliverNetworkResponse();
314 return;
317 if (IsResourceTypeFrame(resource_type_) && cache_id != kAppCacheNoCacheId) {
318 // AppCacheHost loads and holds a reference to the main resource cache
319 // for two reasons, firstly to preload the cache into the working set
320 // in advance of subresource loads happening, secondly to prevent the
321 // AppCache from falling out of the working set on frame navigations.
322 host_->LoadMainResourceCache(cache_id);
323 host_->set_preferred_manifest_url(manifest_url);
326 // 6.11.1 Navigating across documents, steps 10 and 14.
328 found_entry_ = entry;
329 found_namespace_entry_url_ = namespace_entry_url;
330 found_fallback_entry_ = fallback_entry;
331 found_cache_id_ = cache_id;
332 found_group_id_ = group_id;
333 found_manifest_url_ = manifest_url;
334 found_network_namespace_ = false; // not applicable to main requests
336 if (found_entry_.has_response_id()) {
337 DCHECK(!found_fallback_entry_.has_response_id());
338 DeliverAppCachedResponse(
339 found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
340 false, found_namespace_entry_url_);
341 } else {
342 DeliverNetworkResponse();
346 // Sub-resource handling ----------------------------------------------
348 void AppCacheRequestHandler::MaybeLoadSubResource(
349 net::URLRequest* request, net::NetworkDelegate* network_delegate) {
350 DCHECK(!job_.get());
352 if (host_->is_selection_pending()) {
353 // We have to wait until cache selection is complete and the
354 // selected cache is loaded.
355 is_waiting_for_cache_selection_ = true;
356 job_ = new AppCacheURLRequestJob(request, network_delegate,
357 storage(), host_, is_main_resource());
358 return;
361 if (!host_->associated_cache() ||
362 !host_->associated_cache()->is_complete() ||
363 host_->associated_cache()->owning_group()->is_being_deleted()) {
364 return;
367 job_ = new AppCacheURLRequestJob(request, network_delegate,
368 storage(), host_, is_main_resource());
369 ContinueMaybeLoadSubResource();
372 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
373 // 6.9.6 Changes to the networking model
374 // If the resource is not to be fetched using the HTTP GET mechanism or
375 // equivalent ... then fetch the resource normally.
376 DCHECK(job_.get());
377 DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete());
379 const GURL& url = job_->request()->url();
380 AppCache* cache = host_->associated_cache();
381 storage()->FindResponseForSubRequest(
382 host_->associated_cache(), url,
383 &found_entry_, &found_fallback_entry_, &found_network_namespace_);
385 if (found_entry_.has_response_id()) {
386 // Step 2: If there's an entry, get it instead.
387 DCHECK(!found_network_namespace_ &&
388 !found_fallback_entry_.has_response_id());
389 found_cache_id_ = cache->cache_id();
390 found_group_id_ = cache->owning_group()->group_id();
391 found_manifest_url_ = cache->owning_group()->manifest_url();
392 DeliverAppCachedResponse(
393 found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
394 false, GURL());
395 return;
398 if (found_fallback_entry_.has_response_id()) {
399 // Step 4: Fetch the resource normally, if this results
400 // in certain conditions, then use the fallback.
401 DCHECK(!found_network_namespace_ &&
402 !found_entry_.has_response_id());
403 found_cache_id_ = cache->cache_id();
404 found_manifest_url_ = cache->owning_group()->manifest_url();
405 DeliverNetworkResponse();
406 return;
409 if (found_network_namespace_) {
410 // Step 3 and 5: Fetch the resource normally.
411 DCHECK(!found_entry_.has_response_id() &&
412 !found_fallback_entry_.has_response_id());
413 DeliverNetworkResponse();
414 return;
417 // Step 6: Fail the resource load.
418 DeliverErrorResponse();
421 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) {
422 DCHECK(host == host_);
423 if (is_main_resource())
424 return;
425 if (!is_waiting_for_cache_selection_)
426 return;
428 is_waiting_for_cache_selection_ = false;
430 if (!host_->associated_cache() ||
431 !host_->associated_cache()->is_complete()) {
432 DeliverNetworkResponse();
433 return;
436 ContinueMaybeLoadSubResource();
439 } // namespace content