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_policy.h" 11 #include "webkit/browser/appcache/appcache_url_request_job.h" 12 13 namespace appcache { 14 15 AppCacheRequestHandler::AppCacheRequestHandler( 16 AppCacheHost* host, ResourceType::Type resource_type) 17 : host_(host), resource_type_(resource_type), 18 is_waiting_for_cache_selection_(false), found_group_id_(0), 19 found_cache_id_(0), found_network_namespace_(false), 20 cache_entry_not_found_(false), maybe_load_resource_executed_(false) { 21 DCHECK(host_); 22 host_->AddObserver(this); 23 } 24 25 AppCacheRequestHandler::~AppCacheRequestHandler() { 26 if (host_) { 27 storage()->CancelDelegateCallbacks(this); 28 host_->RemoveObserver(this); 29 } 30 } 31 32 AppCacheStorage* AppCacheRequestHandler::storage() const { 33 DCHECK(host_); 34 return host_->service()->storage(); 35 } 36 37 void AppCacheRequestHandler::GetExtraResponseInfo( 38 int64* cache_id, GURL* manifest_url) { 39 if (job_.get() && job_->is_delivering_appcache_response()) { 40 *cache_id = job_->cache_id(); 41 *manifest_url = job_->manifest_url(); 42 } 43 } 44 45 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource( 46 net::URLRequest* request, net::NetworkDelegate* network_delegate) { 47 maybe_load_resource_executed_ = true; 48 if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_) 49 return NULL; 50 51 // This method can get called multiple times over the life 52 // of a request. The case we detect here is having scheduled 53 // delivery of a "network response" using a job setup on an 54 // earlier call thru this method. To send the request thru 55 // to the network involves restarting the request altogether, 56 // which will call thru to our interception layer again. 57 // This time thru, we return NULL so the request hits the wire. 58 if (job_.get()) { 59 DCHECK(job_->is_delivering_network_response() || 60 job_->cache_entry_not_found()); 61 if (job_->cache_entry_not_found()) 62 cache_entry_not_found_ = true; 63 job_ = NULL; 64 storage()->CancelDelegateCallbacks(this); 65 return NULL; 66 } 67 68 // Clear out our 'found' fields since we're starting a request for a 69 // new resource, any values in those fields are no longer valid. 70 found_entry_ = AppCacheEntry(); 71 found_fallback_entry_ = AppCacheEntry(); 72 found_cache_id_ = kNoCacheId; 73 found_manifest_url_ = GURL(); 74 found_network_namespace_ = false; 75 76 if (is_main_resource()) 77 MaybeLoadMainResource(request, network_delegate); 78 else 79 MaybeLoadSubResource(request, network_delegate); 80 81 // If its been setup to deliver a network response, we can just delete 82 // it now and return NULL instead to achieve that since it couldn't 83 // have been started yet. 84 if (job_.get() && job_->is_delivering_network_response()) { 85 DCHECK(!job_->has_been_started()); 86 job_ = NULL; 87 } 88 89 return job_.get(); 90 } 91 92 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect( 93 net::URLRequest* request, 94 net::NetworkDelegate* network_delegate, 95 const GURL& location) { 96 if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_) 97 return NULL; 98 if (is_main_resource()) 99 return NULL; 100 // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of 101 // it once a more general solution to crbug/121325 is in place. 102 if (!maybe_load_resource_executed_) 103 return NULL; 104 if (request->url().GetOrigin() == location.GetOrigin()) 105 return NULL; 106 107 DCHECK(!job_.get()); // our jobs never generate redirects 108 109 if (found_fallback_entry_.has_response_id()) { 110 // 6.9.6, step 4: If this results in a redirect to another origin, 111 // get the resource of the fallback entry. 112 job_ = new AppCacheURLRequestJob(request, network_delegate, 113 storage(), host_); 114 DeliverAppCachedResponse( 115 found_fallback_entry_, found_cache_id_, found_group_id_, 116 found_manifest_url_, true, found_namespace_entry_url_); 117 } else if (!found_network_namespace_) { 118 // 6.9.6, step 6: Fail the resource load. 119 job_ = new AppCacheURLRequestJob(request, network_delegate, 120 storage(), host_); 121 DeliverErrorResponse(); 122 } else { 123 // 6.9.6 step 3 and 5: Fetch the resource normally. 124 } 125 126 return job_.get(); 127 } 128 129 AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse( 130 net::URLRequest* request, net::NetworkDelegate* network_delegate) { 131 if (!host_ || !IsSchemeAndMethodSupported(request) || 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_); 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::OnDestructionImminent(AppCacheHost* host) { 174 storage()->CancelDelegateCallbacks(this); 175 host_ = NULL; // no need to RemoveObserver, the host is being deleted 176 177 // Since the host is being deleted, we don't have to complete any job 178 // that is current running. It's destined for the bit bucket anyway. 179 if (job_.get()) { 180 job_->Kill(); 181 job_ = NULL; 182 } 183 } 184 185 void AppCacheRequestHandler::DeliverAppCachedResponse( 186 const AppCacheEntry& entry, int64 cache_id, int64 group_id, 187 const GURL& manifest_url, bool is_fallback, 188 const GURL& namespace_entry_url) { 189 DCHECK(host_ && job_.get() && job_->is_waiting()); 190 DCHECK(entry.has_response_id()); 191 192 if (ResourceType::IsFrame(resource_type_) && !namespace_entry_url.is_empty()) 193 host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url); 194 195 job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id, 196 entry, is_fallback); 197 } 198 199 void AppCacheRequestHandler::DeliverErrorResponse() { 200 DCHECK(job_.get() && job_->is_waiting()); 201 job_->DeliverErrorResponse(); 202 } 203 204 void AppCacheRequestHandler::DeliverNetworkResponse() { 205 DCHECK(job_.get() && job_->is_waiting()); 206 job_->DeliverNetworkResponse(); 207 } 208 209 // Main-resource handling ---------------------------------------------- 210 211 void AppCacheRequestHandler::MaybeLoadMainResource( 212 net::URLRequest* request, net::NetworkDelegate* network_delegate) { 213 DCHECK(!job_.get()); 214 DCHECK(host_); 215 216 const AppCacheHost* spawning_host = 217 ResourceType::IsSharedWorker(resource_type_) ? 218 host_ : host_->GetSpawningHost(); 219 GURL preferred_manifest_url = spawning_host ? 220 spawning_host->preferred_manifest_url() : GURL(); 221 222 // We may have to wait for our storage query to complete, but 223 // this query can also complete syncrhonously. 224 job_ = new AppCacheURLRequestJob(request, network_delegate, 225 storage(), host_); 226 storage()->FindResponseForMainRequest( 227 request->url(), preferred_manifest_url, this); 228 } 229 230 void AppCacheRequestHandler::OnMainResponseFound( 231 const GURL& url, const AppCacheEntry& entry, 232 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, 233 int64 cache_id, int64 group_id, const GURL& manifest_url) { 234 DCHECK(job_.get()); 235 DCHECK(host_); 236 DCHECK(is_main_resource()); 237 DCHECK(!entry.IsForeign()); 238 DCHECK(!fallback_entry.IsForeign()); 239 DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id())); 240 241 if (!job_.get()) 242 return; 243 244 AppCachePolicy* policy = host_->service()->appcache_policy(); 245 bool was_blocked_by_policy = !manifest_url.is_empty() && policy && 246 !policy->CanLoadAppCache(manifest_url, host_->first_party_url()); 247 248 if (was_blocked_by_policy) { 249 if (ResourceType::IsFrame(resource_type_)) { 250 host_->NotifyMainResourceBlocked(manifest_url); 251 } else { 252 DCHECK(ResourceType::IsSharedWorker(resource_type_)); 253 host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url); 254 } 255 DeliverNetworkResponse(); 256 return; 257 } 258 259 if (ResourceType::IsFrame(resource_type_) && cache_id != kNoCacheId) { 260 // AppCacheHost loads and holds a reference to the main resource cache 261 // for two reasons, firstly to preload the cache into the working set 262 // in advance of subresource loads happening, secondly to prevent the 263 // AppCache from falling out of the working set on frame navigations. 264 host_->LoadMainResourceCache(cache_id); 265 host_->set_preferred_manifest_url(manifest_url); 266 } 267 268 // 6.11.1 Navigating across documents, steps 10 and 14. 269 270 found_entry_ = entry; 271 found_namespace_entry_url_ = namespace_entry_url; 272 found_fallback_entry_ = fallback_entry; 273 found_cache_id_ = cache_id; 274 found_group_id_ = group_id; 275 found_manifest_url_ = manifest_url; 276 found_network_namespace_ = false; // not applicable to main requests 277 278 if (found_entry_.has_response_id()) { 279 DCHECK(!found_fallback_entry_.has_response_id()); 280 DeliverAppCachedResponse( 281 found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, 282 false, found_namespace_entry_url_); 283 } else { 284 DeliverNetworkResponse(); 285 } 286 } 287 288 // Sub-resource handling ---------------------------------------------- 289 290 void AppCacheRequestHandler::MaybeLoadSubResource( 291 net::URLRequest* request, net::NetworkDelegate* network_delegate) { 292 DCHECK(!job_.get()); 293 294 if (host_->is_selection_pending()) { 295 // We have to wait until cache selection is complete and the 296 // selected cache is loaded. 297 is_waiting_for_cache_selection_ = true; 298 job_ = new AppCacheURLRequestJob(request, network_delegate, 299 storage(), host_); 300 return; 301 } 302 303 if (!host_->associated_cache() || 304 !host_->associated_cache()->is_complete()) { 305 return; 306 } 307 308 job_ = new AppCacheURLRequestJob(request, network_delegate, 309 storage(), host_); 310 ContinueMaybeLoadSubResource(); 311 } 312 313 void AppCacheRequestHandler::ContinueMaybeLoadSubResource() { 314 // 6.9.6 Changes to the networking model 315 // If the resource is not to be fetched using the HTTP GET mechanism or 316 // equivalent ... then fetch the resource normally. 317 DCHECK(job_.get()); 318 DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete()); 319 320 const GURL& url = job_->request()->url(); 321 AppCache* cache = host_->associated_cache(); 322 storage()->FindResponseForSubRequest( 323 host_->associated_cache(), url, 324 &found_entry_, &found_fallback_entry_, &found_network_namespace_); 325 326 if (found_entry_.has_response_id()) { 327 // Step 2: If there's an entry, get it instead. 328 DCHECK(!found_network_namespace_ && 329 !found_fallback_entry_.has_response_id()); 330 found_cache_id_ = cache->cache_id(); 331 found_group_id_ = cache->owning_group()->group_id(); 332 found_manifest_url_ = cache->owning_group()->manifest_url(); 333 DeliverAppCachedResponse( 334 found_entry_, found_cache_id_, found_group_id_, found_manifest_url_, 335 false, GURL()); 336 return; 337 } 338 339 if (found_fallback_entry_.has_response_id()) { 340 // Step 4: Fetch the resource normally, if this results 341 // in certain conditions, then use the fallback. 342 DCHECK(!found_network_namespace_ && 343 !found_entry_.has_response_id()); 344 found_cache_id_ = cache->cache_id(); 345 found_manifest_url_ = cache->owning_group()->manifest_url(); 346 DeliverNetworkResponse(); 347 return; 348 } 349 350 if (found_network_namespace_) { 351 // Step 3 and 5: Fetch the resource normally. 352 DCHECK(!found_entry_.has_response_id() && 353 !found_fallback_entry_.has_response_id()); 354 DeliverNetworkResponse(); 355 return; 356 } 357 358 // Step 6: Fail the resource load. 359 DeliverErrorResponse(); 360 } 361 362 void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) { 363 DCHECK(host == host_); 364 if (is_main_resource()) 365 return; 366 if (!is_waiting_for_cache_selection_) 367 return; 368 369 is_waiting_for_cache_selection_ = false; 370 371 if (!host_->associated_cache() || 372 !host_->associated_cache()->is_complete()) { 373 DeliverNetworkResponse(); 374 return; 375 } 376 377 ContinueMaybeLoadSubResource(); 378 } 379 380 } // namespace appcache 381