• 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 "webkit/browser/appcache/appcache_request_handler.h"
6 
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"
13 
14 namespace appcache {
15 
AppCacheRequestHandler(AppCacheHost * host,ResourceType::Type resource_type)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) {
22   DCHECK(host_);
23   host_->AddObserver(this);
24 }
25 
~AppCacheRequestHandler()26 AppCacheRequestHandler::~AppCacheRequestHandler() {
27   if (host_) {
28     storage()->CancelDelegateCallbacks(this);
29     host_->RemoveObserver(this);
30   }
31 }
32 
storage() const33 AppCacheStorage* AppCacheRequestHandler::storage() const {
34   DCHECK(host_);
35   return host_->storage();
36 }
37 
MaybeLoadResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)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_)
42     return NULL;
43 
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.
51   if (job_.get()) {
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;
56     job_ = NULL;
57     storage()->CancelDelegateCallbacks(this);
58     return NULL;
59   }
60 
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_ = kNoCacheId;
66   found_manifest_url_ = GURL();
67   found_network_namespace_ = false;
68 
69   if (is_main_resource())
70     MaybeLoadMainResource(request, network_delegate);
71   else
72     MaybeLoadSubResource(request, network_delegate);
73 
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());
79     job_ = NULL;
80   }
81 
82   return job_.get();
83 }
84 
MaybeLoadFallbackForRedirect(net::URLRequest * request,net::NetworkDelegate * network_delegate,const GURL & location)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_)
90     return NULL;
91   if (is_main_resource())
92     return NULL;
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_)
96     return NULL;
97   if (request->url().GetOrigin() == location.GetOrigin())
98     return NULL;
99 
100   DCHECK(!job_.get());  // our jobs never generate redirects
101 
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_);
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_);
114     DeliverErrorResponse();
115   } else {
116     // 6.9.6 step 3 and 5: Fetch the resource normally.
117   }
118 
119   return job_.get();
120 }
121 
MaybeLoadFallbackForResponse(net::URLRequest * request,net::NetworkDelegate * network_delegate)122 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
123     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
124   if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_)
125     return NULL;
126   if (!found_fallback_entry_.has_response_id())
127     return NULL;
128 
129   if (request->status().status() == net::URLRequestStatus::CANCELED) {
130     // 6.9.6, step 4: But not if the user canceled the download.
131     return NULL;
132   }
133 
134   // We don't fallback for responses that we delivered.
135   if (job_.get()) {
136     DCHECK(!job_->is_delivering_network_response());
137     return NULL;
138   }
139 
140   if (request->status().is_success()) {
141     int code_major = request->GetResponseCode() / 100;
142     if (code_major !=4 && code_major != 5)
143       return NULL;
144 
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)
153       return NULL;
154   }
155 
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_);
160   DeliverAppCachedResponse(
161       found_fallback_entry_, found_cache_id_, found_group_id_,
162       found_manifest_url_, true, found_namespace_entry_url_);
163   return job_.get();
164 }
165 
GetExtraResponseInfo(int64 * cache_id,GURL * manifest_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();
171   }
172 }
173 
PrepareForCrossSiteTransfer(int old_process_id)174 void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) {
175   if (!host_)
176     return;
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());
180 }
181 
CompleteCrossSiteTransfer(int new_process_id,int new_host_id)182 void AppCacheRequestHandler::CompleteCrossSiteTransfer(
183     int new_process_id, int new_host_id) {
184   if (!host_for_cross_site_transfer_.get())
185     return;
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());
189 }
190 
OnDestructionImminent(AppCacheHost * host)191 void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) {
192   storage()->CancelDelegateCallbacks(this);
193   host_ = NULL;  // no need to RemoveObserver, the host is being deleted
194 
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.
197   if (job_.get()) {
198     job_->Kill();
199     job_ = NULL;
200   }
201 }
202 
DeliverAppCachedResponse(const AppCacheEntry & entry,int64 cache_id,int64 group_id,const GURL & manifest_url,bool is_fallback,const GURL & namespace_entry_url)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());
209 
210   if (ResourceType::IsFrame(resource_type_) && !namespace_entry_url.is_empty())
211     host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url);
212 
213   job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id,
214                                  entry, is_fallback);
215 }
216 
DeliverErrorResponse()217 void AppCacheRequestHandler::DeliverErrorResponse() {
218   DCHECK(job_.get() && job_->is_waiting());
219   job_->DeliverErrorResponse();
220 }
221 
DeliverNetworkResponse()222 void AppCacheRequestHandler::DeliverNetworkResponse() {
223   DCHECK(job_.get() && job_->is_waiting());
224   job_->DeliverNetworkResponse();
225 }
226 
227 // Main-resource handling ----------------------------------------------
228 
MaybeLoadMainResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)229 void AppCacheRequestHandler::MaybeLoadMainResource(
230     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
231   DCHECK(!job_.get());
232   DCHECK(host_);
233 
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();
239 
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_);
244   storage()->FindResponseForMainRequest(
245       request->url(), preferred_manifest_url, this);
246 }
247 
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)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) {
252   DCHECK(job_.get());
253   DCHECK(host_);
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()));
258 
259   if (!job_.get())
260     return;
261 
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());
265 
266   if (was_blocked_by_policy) {
267     if (ResourceType::IsFrame(resource_type_)) {
268       host_->NotifyMainResourceBlocked(manifest_url);
269     } else {
270       DCHECK(ResourceType::IsSharedWorker(resource_type_));
271       host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url);
272     }
273     DeliverNetworkResponse();
274     return;
275   }
276 
277   if (ResourceType::IsFrame(resource_type_) && cache_id != kNoCacheId) {
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);
284   }
285 
286   // 6.11.1 Navigating across documents, steps 10 and 14.
287 
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
295 
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_);
301   } else {
302     DeliverNetworkResponse();
303   }
304 }
305 
306 // Sub-resource handling ----------------------------------------------
307 
MaybeLoadSubResource(net::URLRequest * request,net::NetworkDelegate * network_delegate)308 void AppCacheRequestHandler::MaybeLoadSubResource(
309     net::URLRequest* request, net::NetworkDelegate* network_delegate) {
310   DCHECK(!job_.get());
311 
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_);
318     return;
319   }
320 
321   if (!host_->associated_cache() ||
322       !host_->associated_cache()->is_complete()) {
323     return;
324   }
325 
326   job_ = new AppCacheURLRequestJob(request, network_delegate,
327                                    storage(), host_);
328   ContinueMaybeLoadSubResource();
329 }
330 
ContinueMaybeLoadSubResource()331 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
332   // 6.9.6 Changes to the networking model
333   // If the resource is not to be fetched using the HTTP GET mechanism or
334   // equivalent ... then fetch the resource normally.
335   DCHECK(job_.get());
336   DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete());
337 
338   const GURL& url = job_->request()->url();
339   AppCache* cache = host_->associated_cache();
340   storage()->FindResponseForSubRequest(
341       host_->associated_cache(), url,
342       &found_entry_, &found_fallback_entry_, &found_network_namespace_);
343 
344   if (found_entry_.has_response_id()) {
345     // Step 2: If there's an entry, get it instead.
346     DCHECK(!found_network_namespace_ &&
347            !found_fallback_entry_.has_response_id());
348     found_cache_id_ = cache->cache_id();
349     found_group_id_ = cache->owning_group()->group_id();
350     found_manifest_url_ = cache->owning_group()->manifest_url();
351     DeliverAppCachedResponse(
352         found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
353         false, GURL());
354     return;
355   }
356 
357   if (found_fallback_entry_.has_response_id()) {
358     // Step 4: Fetch the resource normally, if this results
359     // in certain conditions, then use the fallback.
360     DCHECK(!found_network_namespace_ &&
361            !found_entry_.has_response_id());
362     found_cache_id_ = cache->cache_id();
363     found_manifest_url_ = cache->owning_group()->manifest_url();
364     DeliverNetworkResponse();
365     return;
366   }
367 
368   if (found_network_namespace_) {
369     // Step 3 and 5: Fetch the resource normally.
370     DCHECK(!found_entry_.has_response_id() &&
371            !found_fallback_entry_.has_response_id());
372     DeliverNetworkResponse();
373     return;
374   }
375 
376   // Step 6: Fail the resource load.
377   DeliverErrorResponse();
378 }
379 
OnCacheSelectionComplete(AppCacheHost * host)380 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) {
381   DCHECK(host == host_);
382   if (is_main_resource())
383     return;
384   if (!is_waiting_for_cache_selection_)
385     return;
386 
387   is_waiting_for_cache_selection_ = false;
388 
389   if (!host_->associated_cache() ||
390       !host_->associated_cache()->is_complete()) {
391     DeliverNetworkResponse();
392     return;
393   }
394 
395   ContinueMaybeLoadSubResource();
396 }
397 
398 }  // namespace appcache
399