| 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 |