OLD | NEW |
| (Empty) |
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 | |
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 | |
26 AppCacheRequestHandler::~AppCacheRequestHandler() { | |
27 if (host_) { | |
28 storage()->CancelDelegateCallbacks(this); | |
29 host_->RemoveObserver(this); | |
30 } | |
31 } | |
32 | |
33 AppCacheStorage* AppCacheRequestHandler::storage() const { | |
34 DCHECK(host_); | |
35 return host_->storage(); | |
36 } | |
37 | |
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_ = kAppCacheNoCacheId; | |
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 | |
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_, is_main_resource()); | |
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_, is_main_resource()); | |
114 DeliverErrorResponse(); | |
115 } else { | |
116 // 6.9.6 step 3 and 5: Fetch the resource normally. | |
117 } | |
118 | |
119 return job_.get(); | |
120 } | |
121 | |
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_, is_main_resource()); | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
217 void AppCacheRequestHandler::DeliverErrorResponse() { | |
218 DCHECK(job_.get() && job_->is_waiting()); | |
219 job_->DeliverErrorResponse(); | |
220 } | |
221 | |
222 void AppCacheRequestHandler::DeliverNetworkResponse() { | |
223 DCHECK(job_.get() && job_->is_waiting()); | |
224 job_->DeliverNetworkResponse(); | |
225 } | |
226 | |
227 // Main-resource handling ---------------------------------------------- | |
228 | |
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_, is_main_resource()); | |
244 storage()->FindResponseForMainRequest( | |
245 request->url(), preferred_manifest_url, this); | |
246 } | |
247 | |
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 != kAppCacheNoCacheId) { | |
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 | |
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_, is_main_resource()); | |
318 return; | |
319 } | |
320 | |
321 if (!host_->associated_cache() || | |
322 !host_->associated_cache()->is_complete() || | |
323 host_->associated_cache()->owning_group()->is_being_deleted()) { | |
324 return; | |
325 } | |
326 | |
327 job_ = new AppCacheURLRequestJob(request, network_delegate, | |
328 storage(), host_, is_main_resource()); | |
329 ContinueMaybeLoadSubResource(); | |
330 } | |
331 | |
332 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() { | |
333 // 6.9.6 Changes to the networking model | |
334 // If the resource is not to be fetched using the HTTP GET mechanism or | |
335 // equivalent ... then fetch the resource normally. | |
336 DCHECK(job_.get()); | |
337 DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete()); | |
338 | |
339 const GURL& url = job_->request()->url(); | |
340 AppCache* cache = host_->associated_cache(); | |
341 storage()->FindResponseForSubRequest( | |
342 host_->associated_cache(), url, | |
343 &found_entry_, &found_fallback_entry_, &found_network_namespace_); | |
344 | |
345 if (found_entry_.has_response_id()) { | |
346 // Step 2: If there's an entry, get it instead. | |
347 DCHECK(!found_network_namespace_ && | |
348 !found_fallback_entry_.has_response_id()); | |
349 found_cache_id_ = cache->cache_id(); | |
350 found_group_id_ = cache->owning_group()->group_id(); | |
351 found_manifest_url_ = cache->owning_group()->manifest_url(); | |
352 DeliverAppCachedResponse( | |
353 found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, | |
354 false, GURL()); | |
355 return; | |
356 } | |
357 | |
358 if (found_fallback_entry_.has_response_id()) { | |
359 // Step 4: Fetch the resource normally, if this results | |
360 // in certain conditions, then use the fallback. | |
361 DCHECK(!found_network_namespace_ && | |
362 !found_entry_.has_response_id()); | |
363 found_cache_id_ = cache->cache_id(); | |
364 found_manifest_url_ = cache->owning_group()->manifest_url(); | |
365 DeliverNetworkResponse(); | |
366 return; | |
367 } | |
368 | |
369 if (found_network_namespace_) { | |
370 // Step 3 and 5: Fetch the resource normally. | |
371 DCHECK(!found_entry_.has_response_id() && | |
372 !found_fallback_entry_.has_response_id()); | |
373 DeliverNetworkResponse(); | |
374 return; | |
375 } | |
376 | |
377 // Step 6: Fail the resource load. | |
378 DeliverErrorResponse(); | |
379 } | |
380 | |
381 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) { | |
382 DCHECK(host == host_); | |
383 if (is_main_resource()) | |
384 return; | |
385 if (!is_waiting_for_cache_selection_) | |
386 return; | |
387 | |
388 is_waiting_for_cache_selection_ = false; | |
389 | |
390 if (!host_->associated_cache() || | |
391 !host_->associated_cache()->is_complete()) { | |
392 DeliverNetworkResponse(); | |
393 return; | |
394 } | |
395 | |
396 ContinueMaybeLoadSubResource(); | |
397 } | |
398 | |
399 } // namespace appcache | |
OLD | NEW |