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_ = 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 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 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 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_); 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 != 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 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 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 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