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 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 30 AppCacheRequestHandler::~AppCacheRequestHandler() { 31 if (host_) { 32 storage()->CancelDelegateCallbacks(this); 33 host_->RemoveObserver(this); 34 } 35 } 36 37 AppCacheStorage* AppCacheRequestHandler::storage() const { 38 DCHECK(host_); 39 return host_->storage(); 40 } 41 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 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 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 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 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 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 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 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 224 void AppCacheRequestHandler::DeliverErrorResponse() { 225 DCHECK(job_.get() && job_->is_waiting()); 226 job_->DeliverErrorResponse(); 227 } 228 229 void AppCacheRequestHandler::DeliverNetworkResponse() { 230 DCHECK(job_.get() && job_->is_waiting()); 231 job_->DeliverNetworkResponse(); 232 } 233 234 // Main-resource handling ---------------------------------------------- 235 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 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 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 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 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