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