• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "content/browser/appcache/appcache_request_handler.h"
6 
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 "net/url_request/url_request.h"
12 #include "net/url_request/url_request_job.h"
13 
14 namespace content {
15 
AppCacheRequestHandler(AppCacheHost * host,ResourceType resource_type)16 AppCacheRequestHandler::AppCacheRequestHandler(AppCacheHost* host,
17                                                ResourceType resource_type)
18     : host_(host),
19       resource_type_(resource_type),
20       is_waiting_for_cache_selection_(false),
21       found_group_id_(0),
22       found_cache_id_(0),
23       found_network_namespace_(false),
24       cache_entry_not_found_(false),
25       maybe_load_resource_executed_(false) {
26   DCHECK(host_);
27   host_->AddObserver(this);
28 }
29 
~AppCacheRequestHandler()30 AppCacheRequestHandler::~AppCacheRequestHandler() {
31   if (host_) {
32     storage()->CancelDelegateCallbacks(this);
33     host_->RemoveObserver(this);
34   }
35 }
36 
storage() const37 AppCacheStorage* AppCacheRequestHandler::storage() const {
38   DCHECK(host_);
39   return host_->storage();
40 }
41 
MaybeLoadResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)42 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource(
43     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
44   maybe_load_resource_executed_ = true;
45   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
46       cache_entry_not_found_)
47     return NULL;
48 
49   // This method can get called multiple times over the life
50   // of a request. The case we detect here is having scheduled
51   // delivery of a "network response" using a job setup on an
52   // earlier call thru this method. To send the request thru
53   // to the network involves restarting the request altogether,
54   // which will call thru to our interception layer again.
55   // This time thru, we return NULL so the request hits the wire.
56   if (job_.get()) {
57     DCHECK(job_->is_delivering_network_response() ||
58            job_->cache_entry_not_found());
59     if (job_->cache_entry_not_found())
60       cache_entry_not_found_ = true;
61     job_ = NULL;
62     storage()->CancelDelegateCallbacks(this);
63     return NULL;
64   }
65 
66   // Clear out our 'found' fields since we're starting a request for a
67   // new resource, any values in those fields are no longer valid.
68   found_entry_ = AppCacheEntry();
69   found_fallback_entry_ = AppCacheEntry();
70   found_cache_id_ = kAppCacheNoCacheId;
71   found_manifest_url_ = GURL();
72   found_network_namespace_ = false;
73 
74   if (is_main_resource())
75     MaybeLoadMainResource(request, network_delegate);
76   else
77     MaybeLoadSubResource(request, network_delegate);
78 
79   // If its been setup to deliver a network response, we can just delete
80   // it now and return NULL instead to achieve that since it couldn't
81   // have been started yet.
82   if (job_.get() && job_->is_delivering_network_response()) {
83     DCHECK(!job_->has_been_started());
84     job_ = NULL;
85   }
86 
87   return job_.get();
88 }
89 
MaybeLoadFallbackForRedirect(net::URLRequest * request,net::NetworkDelegate * network_delegate,const GURL & location)90 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
91     net::URLRequest* request,
92     net::NetworkDelegate* network_delegate,
93     const GURL& location) {
94   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
95       cache_entry_not_found_)
96     return NULL;
97   if (is_main_resource())
98     return NULL;
99   // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
100   // it once a more general solution to crbug/121325 is in place.
101   if (!maybe_load_resource_executed_)
102     return NULL;
103   if (request->url().GetOrigin() == location.GetOrigin())
104     return NULL;
105 
106   DCHECK(!job_.get());  // our jobs never generate redirects
107 
108   if (found_fallback_entry_.has_response_id()) {
109     // 6.9.6, step 4: If this results in a redirect to another origin,
110     // get the resource of the fallback entry.
111     job_ = new AppCacheURLRequestJob(request, network_delegate,
112                                      storage(), host_, is_main_resource());
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,
119                                      storage(), host_, is_main_resource());
120     DeliverErrorResponse();
121   } else {
122     // 6.9.6 step 3 and 5: Fetch the resource normally.
123   }
124 
125   return job_.get();
126 }
127 
MaybeLoadFallbackForResponse(net::URLRequest * request,net::NetworkDelegate * network_delegate)128 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
129     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
130   if (!host_ || !IsSchemeAndMethodSupportedForAppCache(request) ||
131       cache_entry_not_found_)
132     return NULL;
133   if (!found_fallback_entry_.has_response_id())
134     return NULL;
135 
136   if (request->status().status() == net::URLRequestStatus::CANCELED) {
137     // 6.9.6, step 4: But not if the user canceled the download.
138     return NULL;
139   }
140 
141   // We don't fallback for responses that we delivered.
142   if (job_.get()) {
143     DCHECK(!job_->is_delivering_network_response());
144     return NULL;
145   }
146 
147   if (request->status().is_success()) {
148     int code_major = request->GetResponseCode() / 100;
149     if (code_major !=4 && code_major != 5)
150       return NULL;
151 
152     // Servers can override the fallback behavior with a response header.
153     const std::string kFallbackOverrideHeader(
154         "x-chromium-appcache-fallback-override");
155     const std::string kFallbackOverrideValue(
156         "disallow-fallback");
157     std::string header_value;
158     request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value);
159     if (header_value == kFallbackOverrideValue)
160       return NULL;
161   }
162 
163   // 6.9.6, step 4: If this results in a 4xx or 5xx status code
164   // or there were network errors, get the resource of the fallback entry.
165   job_ = new AppCacheURLRequestJob(request, network_delegate,
166                                    storage(), host_, is_main_resource());
167   DeliverAppCachedResponse(
168       found_fallback_entry_, found_cache_id_, found_group_id_,
169       found_manifest_url_, true, found_namespace_entry_url_);
170   return job_.get();
171 }
172 
GetExtraResponseInfo(int64 * cache_id,GURL * manifest_url)173 void AppCacheRequestHandler::GetExtraResponseInfo(
174     int64* cache_id, GURL* manifest_url) {
175   if (job_.get() && job_->is_delivering_appcache_response()) {
176     *cache_id = job_->cache_id();
177     *manifest_url = job_->manifest_url();
178   }
179 }
180 
PrepareForCrossSiteTransfer(int old_process_id)181 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) {
182   if (!host_)
183     return;
184   AppCacheBackendImpl* backend = host_->service()->GetBackend(old_process_id);
185   host_for_cross_site_transfer_ = backend->TransferHostOut(host_->host_id());
186   DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
187 }
188 
CompleteCrossSiteTransfer(int new_process_id,int new_host_id)189 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
190     int new_process_id, int new_host_id) {
191   if (!host_for_cross_site_transfer_.get())
192     return;
193   DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
194   AppCacheBackendImpl* backend = host_->service()->GetBackend(new_process_id);
195   backend->TransferHostIn(new_host_id, host_for_cross_site_transfer_.Pass());
196 }
197 
OnDestructionImminent(AppCacheHost * host)198 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) {
199   storage()->CancelDelegateCallbacks(this);
200   host_ = NULL;  // no need to RemoveObserver, the host is being deleted
201 
202   // Since the host is being deleted, we don't have to complete any job
203   // that is current running. It's destined for the bit bucket anyway.
204   if (job_.get()) {
205     job_->Kill();
206     job_ = NULL;
207   }
208 }
209 
DeliverAppCachedResponse(const AppCacheEntry & entry,int64 cache_id,int64 group_id,const GURL & manifest_url,bool is_fallback,const GURL & namespace_entry_url)210 void AppCacheRequestHandler::DeliverAppCachedResponse(
211     const AppCacheEntry& entry, int64 cache_id, int64 group_id,
212     const GURL& manifest_url,  bool is_fallback,
213     const GURL& namespace_entry_url) {
214   DCHECK(host_ && job_.get() && job_->is_waiting());
215   DCHECK(entry.has_response_id());
216 
217   if (IsResourceTypeFrame(resource_type_) && !namespace_entry_url.is_empty())
218     host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url);
219 
220   job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id,
221                                  entry, is_fallback);
222 }
223 
DeliverErrorResponse()224 void AppCacheRequestHandler::DeliverErrorResponse() {
225   DCHECK(job_.get() && job_->is_waiting());
226   job_->DeliverErrorResponse();
227 }
228 
DeliverNetworkResponse()229 void AppCacheRequestHandler::DeliverNetworkResponse() {
230   DCHECK(job_.get() && job_->is_waiting());
231   job_->DeliverNetworkResponse();
232 }
233 
234 // Main-resource handling ----------------------------------------------
235 
MaybeLoadMainResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)236 void AppCacheRequestHandler::MaybeLoadMainResource(
237     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
238   DCHECK(!job_.get());
239   DCHECK(host_);
240 
241   const AppCacheHost* spawning_host =
242       (resource_type_ == RESOURCE_TYPE_SHARED_WORKER) ?
243       host_ : host_->GetSpawningHost();
244   GURL preferred_manifest_url = spawning_host ?
245       spawning_host->preferred_manifest_url() : GURL();
246 
247   // We may have to wait for our storage query to complete, but
248   // this query can also complete syncrhonously.
249   job_ = new AppCacheURLRequestJob(request, network_delegate,
250                                    storage(), host_, is_main_resource());
251   storage()->FindResponseForMainRequest(
252       request->url(), preferred_manifest_url, this);
253 }
254 
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)255 void AppCacheRequestHandler::OnMainResponseFound(
256     const GURL& url, const AppCacheEntry& entry,
257     const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
258     int64 cache_id, int64 group_id, const GURL& manifest_url) {
259   DCHECK(job_.get());
260   DCHECK(host_);
261   DCHECK(is_main_resource());
262   DCHECK(!entry.IsForeign());
263   DCHECK(!fallback_entry.IsForeign());
264   DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id()));
265 
266   if (!job_.get())
267     return;
268 
269   AppCachePolicy* policy = host_->service()->appcache_policy();
270   bool was_blocked_by_policy = !manifest_url.is_empty() && policy &&
271       !policy->CanLoadAppCache(manifest_url, host_->first_party_url());
272 
273   if (was_blocked_by_policy) {
274     if (IsResourceTypeFrame(resource_type_)) {
275       host_->NotifyMainResourceBlocked(manifest_url);
276     } else {
277       DCHECK_EQ(resource_type_, RESOURCE_TYPE_SHARED_WORKER);
278       host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url);
279     }
280     DeliverNetworkResponse();
281     return;
282   }
283 
284   if (IsResourceTypeFrame(resource_type_) && cache_id != kAppCacheNoCacheId) {
285     // AppCacheHost loads and holds a reference to the main resource cache
286     // for two reasons, firstly to preload the cache into the working set
287     // in advance of subresource loads happening, secondly to prevent the
288     // AppCache from falling out of the working set on frame navigations.
289     host_->LoadMainResourceCache(cache_id);
290     host_->set_preferred_manifest_url(manifest_url);
291   }
292 
293   // 6.11.1 Navigating across documents, steps 10 and 14.
294 
295   found_entry_ = entry;
296   found_namespace_entry_url_ = namespace_entry_url;
297   found_fallback_entry_ = fallback_entry;
298   found_cache_id_ = cache_id;
299   found_group_id_ = group_id;
300   found_manifest_url_ = manifest_url;
301   found_network_namespace_ = false;  // not applicable to main requests
302 
303   if (found_entry_.has_response_id()) {
304     DCHECK(!found_fallback_entry_.has_response_id());
305     DeliverAppCachedResponse(
306         found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
307         false, found_namespace_entry_url_);
308   } else {
309     DeliverNetworkResponse();
310   }
311 }
312 
313 // Sub-resource handling ----------------------------------------------
314 
MaybeLoadSubResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)315 void AppCacheRequestHandler::MaybeLoadSubResource(
316     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
317   DCHECK(!job_.get());
318 
319   if (host_->is_selection_pending()) {
320     // We have to wait until cache selection is complete and the
321     // selected cache is loaded.
322     is_waiting_for_cache_selection_ = true;
323     job_ = new AppCacheURLRequestJob(request, network_delegate,
324                                      storage(), host_, is_main_resource());
325     return;
326   }
327 
328   if (!host_->associated_cache() ||
329       !host_->associated_cache()->is_complete() ||
330       host_->associated_cache()->owning_group()->is_being_deleted()) {
331     return;
332   }
333 
334   job_ = new AppCacheURLRequestJob(request, network_delegate,
335                                    storage(), host_, is_main_resource());
336   ContinueMaybeLoadSubResource();
337 }
338 
ContinueMaybeLoadSubResource()339 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
340   // 6.9.6 Changes to the networking model
341   // If the resource is not to be fetched using the HTTP GET mechanism or
342   // equivalent ... then fetch the resource normally.
343   DCHECK(job_.get());
344   DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete());
345 
346   const GURL& url = job_->request()->url();
347   AppCache* cache = host_->associated_cache();
348   storage()->FindResponseForSubRequest(
349       host_->associated_cache(), url,
350       &found_entry_, &found_fallback_entry_, &found_network_namespace_);
351 
352   if (found_entry_.has_response_id()) {
353     // Step 2: If there's an entry, get it instead.
354     DCHECK(!found_network_namespace_ &&
355            !found_fallback_entry_.has_response_id());
356     found_cache_id_ = cache->cache_id();
357     found_group_id_ = cache->owning_group()->group_id();
358     found_manifest_url_ = cache->owning_group()->manifest_url();
359     DeliverAppCachedResponse(
360         found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
361         false, GURL());
362     return;
363   }
364 
365   if (found_fallback_entry_.has_response_id()) {
366     // Step 4: Fetch the resource normally, if this results
367     // in certain conditions, then use the fallback.
368     DCHECK(!found_network_namespace_ &&
369            !found_entry_.has_response_id());
370     found_cache_id_ = cache->cache_id();
371     found_manifest_url_ = cache->owning_group()->manifest_url();
372     DeliverNetworkResponse();
373     return;
374   }
375 
376   if (found_network_namespace_) {
377     // Step 3 and 5: Fetch the resource normally.
378     DCHECK(!found_entry_.has_response_id() &&
379            !found_fallback_entry_.has_response_id());
380     DeliverNetworkResponse();
381     return;
382   }
383 
384   // Step 6: Fail the resource load.
385   DeliverErrorResponse();
386 }
387 
OnCacheSelectionComplete(AppCacheHost * host)388 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) {
389   DCHECK(host == host_);
390   if (is_main_resource())
391     return;
392   if (!is_waiting_for_cache_selection_)
393     return;
394 
395   is_waiting_for_cache_selection_ = false;
396 
397   if (!host_->associated_cache() ||
398       !host_->associated_cache()->is_complete()) {
399     DeliverNetworkResponse();
400     return;
401   }
402 
403   ContinueMaybeLoadSubResource();
404 }
405 
406 }  // namespace content
407