1 // Copyright (c) 2012 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_url_request_job.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/command_line.h" 12 #include "base/compiler_specific.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "content/browser/appcache/appcache.h" 17 #include "content/browser/appcache/appcache_group.h" 18 #include "content/browser/appcache/appcache_histograms.h" 19 #include "content/browser/appcache/appcache_host.h" 20 #include "content/browser/appcache/appcache_service_impl.h" 21 #include "net/base/io_buffer.h" 22 #include "net/base/net_errors.h" 23 #include "net/base/net_log.h" 24 #include "net/http/http_request_headers.h" 25 #include "net/http/http_response_headers.h" 26 #include "net/http/http_util.h" 27 #include "net/url_request/url_request.h" 28 #include "net/url_request/url_request_status.h" 29 30 namespace content { 31 32 AppCacheURLRequestJob::AppCacheURLRequestJob( 33 net::URLRequest* request, 34 net::NetworkDelegate* network_delegate, 35 AppCacheStorage* storage, 36 AppCacheHost* host, 37 bool is_main_resource) 38 : net::URLRequestJob(request, network_delegate), 39 host_(host), 40 storage_(storage), 41 has_been_started_(false), has_been_killed_(false), 42 delivery_type_(AWAITING_DELIVERY_ORDERS), 43 group_id_(0), cache_id_(kAppCacheNoCacheId), is_fallback_(false), 44 is_main_resource_(is_main_resource), 45 cache_entry_not_found_(false), 46 weak_factory_(this) { 47 DCHECK(storage_); 48 } 49 50 void AppCacheURLRequestJob::DeliverAppCachedResponse( 51 const GURL& manifest_url, int64 group_id, int64 cache_id, 52 const AppCacheEntry& entry, bool is_fallback) { 53 DCHECK(!has_delivery_orders()); 54 DCHECK(entry.has_response_id()); 55 delivery_type_ = APPCACHED_DELIVERY; 56 manifest_url_ = manifest_url; 57 group_id_ = group_id; 58 cache_id_ = cache_id; 59 entry_ = entry; 60 is_fallback_ = is_fallback; 61 MaybeBeginDelivery(); 62 } 63 64 void AppCacheURLRequestJob::DeliverNetworkResponse() { 65 DCHECK(!has_delivery_orders()); 66 delivery_type_ = NETWORK_DELIVERY; 67 storage_ = NULL; // not needed 68 MaybeBeginDelivery(); 69 } 70 71 void AppCacheURLRequestJob::DeliverErrorResponse() { 72 DCHECK(!has_delivery_orders()); 73 delivery_type_ = ERROR_DELIVERY; 74 storage_ = NULL; // not needed 75 MaybeBeginDelivery(); 76 } 77 78 void AppCacheURLRequestJob::MaybeBeginDelivery() { 79 if (has_been_started() && has_delivery_orders()) { 80 // Start asynchronously so that all error reporting and data 81 // callbacks happen as they would for network requests. 82 base::MessageLoop::current()->PostTask( 83 FROM_HERE, 84 base::Bind(&AppCacheURLRequestJob::BeginDelivery, 85 weak_factory_.GetWeakPtr())); 86 } 87 } 88 89 void AppCacheURLRequestJob::BeginDelivery() { 90 DCHECK(has_delivery_orders() && has_been_started()); 91 92 if (has_been_killed()) 93 return; 94 95 switch (delivery_type_) { 96 case NETWORK_DELIVERY: 97 AppCacheHistograms::AddNetworkJobStartDelaySample( 98 base::TimeTicks::Now() - start_time_tick_); 99 // To fallthru to the network, we restart the request which will 100 // cause a new job to be created to retrieve the resource from the 101 // network. Our caller is responsible for arranging to not re-intercept 102 // the same request. 103 NotifyRestartRequired(); 104 break; 105 106 case ERROR_DELIVERY: 107 AppCacheHistograms::AddErrorJobStartDelaySample( 108 base::TimeTicks::Now() - start_time_tick_); 109 request()->net_log().AddEvent( 110 net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE); 111 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, 112 net::ERR_FAILED)); 113 break; 114 115 case APPCACHED_DELIVERY: 116 if (entry_.IsExecutable()) { 117 BeginExecutableHandlerDelivery(); 118 return; 119 } 120 AppCacheHistograms::AddAppCacheJobStartDelaySample( 121 base::TimeTicks::Now() - start_time_tick_); 122 request()->net_log().AddEvent( 123 is_fallback_ ? 124 net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE : 125 net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE); 126 storage_->LoadResponseInfo( 127 manifest_url_, group_id_, entry_.response_id(), this); 128 break; 129 130 default: 131 NOTREACHED(); 132 break; 133 } 134 } 135 136 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() { 137 DCHECK(CommandLine::ForCurrentProcess()-> 138 HasSwitch(kEnableExecutableHandlers)); 139 if (!storage_->service()->handler_factory()) { 140 BeginErrorDelivery("missing handler factory"); 141 return; 142 } 143 144 request()->net_log().AddEvent( 145 net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE); 146 147 // We defer job delivery until the executable handler is spun up and 148 // provides a response. The sequence goes like this... 149 // 150 // 1. First we load the cache. 151 // 2. Then if the handler is not spun up, we load the script resource which 152 // is needed to spin it up. 153 // 3. Then we ask then we ask the handler to compute a response. 154 // 4. Finally we deilver that response to the caller. 155 storage_->LoadCache(cache_id_, this); 156 } 157 158 void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) { 159 DCHECK_EQ(cache_id_, cache_id); 160 DCHECK(!has_been_killed()); 161 162 if (!cache) { 163 BeginErrorDelivery("cache load failed"); 164 return; 165 } 166 167 // Keep references to ensure they don't go out of scope until job completion. 168 cache_ = cache; 169 group_ = cache->owning_group(); 170 171 // If the handler is spun up, ask it to compute a response. 172 AppCacheExecutableHandler* handler = 173 cache->GetExecutableHandler(entry_.response_id()); 174 if (handler) { 175 InvokeExecutableHandler(handler); 176 return; 177 } 178 179 // Handler is not spun up yet, load the script resource to do that. 180 // NOTE: This is not ideal since multiple jobs may be doing this, 181 // concurrently but close enough for now, the first to load the script 182 // will win. 183 184 // Read the script data, truncating if its too large. 185 // NOTE: we just issue one read and don't bother chaining if the resource 186 // is very (very) large, close enough for now. 187 const int64 kLimit = 500 * 1000; 188 handler_source_buffer_ = new net::GrowableIOBuffer(); 189 handler_source_buffer_->SetCapacity(kLimit); 190 handler_source_reader_.reset(storage_->CreateResponseReader( 191 manifest_url_, group_id_, entry_.response_id())); 192 handler_source_reader_->ReadData( 193 handler_source_buffer_.get(), 194 kLimit, 195 base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded, 196 base::Unretained(this))); 197 } 198 199 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) { 200 DCHECK(!has_been_killed()); 201 handler_source_reader_.reset(); 202 if (result < 0) { 203 BeginErrorDelivery("script source load failed"); 204 return; 205 } 206 207 handler_source_buffer_->SetCapacity(result); // Free up some memory. 208 209 AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler( 210 entry_.response_id(), handler_source_buffer_.get()); 211 handler_source_buffer_ = NULL; // not needed anymore 212 if (handler) { 213 InvokeExecutableHandler(handler); 214 return; 215 } 216 217 BeginErrorDelivery("factory failed to produce a handler"); 218 } 219 220 void AppCacheURLRequestJob::InvokeExecutableHandler( 221 AppCacheExecutableHandler* handler) { 222 handler->HandleRequest( 223 request(), 224 base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback, 225 weak_factory_.GetWeakPtr())); 226 } 227 228 void AppCacheURLRequestJob::OnExecutableResponseCallback( 229 const AppCacheExecutableHandler::Response& response) { 230 DCHECK(!has_been_killed()); 231 if (response.use_network) { 232 delivery_type_ = NETWORK_DELIVERY; 233 storage_ = NULL; 234 BeginDelivery(); 235 return; 236 } 237 238 if (!response.cached_resource_url.is_empty()) { 239 AppCacheEntry* entry_ptr = cache_->GetEntry(response.cached_resource_url); 240 if (entry_ptr && !entry_ptr->IsExecutable()) { 241 entry_ = *entry_ptr; 242 BeginDelivery(); 243 return; 244 } 245 } 246 247 if (!response.redirect_url.is_empty()) { 248 // TODO(michaeln): playback a redirect 249 // response_headers_(new HttpResponseHeaders(response_headers)), 250 // fallthru for now to deliver an error 251 } 252 253 // Otherwise, return an error. 254 BeginErrorDelivery("handler returned an invalid response"); 255 } 256 257 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) { 258 if (host_) 259 host_->frontend()->OnLogMessage(host_->host_id(), APPCACHE_LOG_ERROR, 260 message); 261 delivery_type_ = ERROR_DELIVERY; 262 storage_ = NULL; 263 BeginDelivery(); 264 } 265 266 AppCacheURLRequestJob::~AppCacheURLRequestJob() { 267 if (storage_) 268 storage_->CancelDelegateCallbacks(this); 269 } 270 271 void AppCacheURLRequestJob::OnResponseInfoLoaded( 272 AppCacheResponseInfo* response_info, int64 response_id) { 273 DCHECK(is_delivering_appcache_response()); 274 scoped_refptr<AppCacheURLRequestJob> protect(this); 275 if (response_info) { 276 info_ = response_info; 277 reader_.reset(storage_->CreateResponseReader( 278 manifest_url_, group_id_, entry_.response_id())); 279 280 if (is_range_request()) 281 SetupRangeResponse(); 282 283 NotifyHeadersComplete(); 284 } else { 285 if (storage_->service()->storage() == storage_) { 286 // A resource that is expected to be in the appcache is missing. 287 // See http://code.google.com/p/chromium/issues/detail?id=50657 288 // Instead of failing the request, we restart the request. The retry 289 // attempt will fallthru to the network instead of trying to load 290 // from the appcache. 291 storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, 292 entry_.response_id()); 293 AppCacheHistograms::CountResponseRetrieval( 294 false, is_main_resource_, manifest_url_.GetOrigin()); 295 } 296 cache_entry_not_found_ = true; 297 NotifyRestartRequired(); 298 } 299 } 300 301 const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const { 302 if (!info_.get()) 303 return NULL; 304 if (range_response_info_) 305 return range_response_info_.get(); 306 return info_->http_response_info(); 307 } 308 309 void AppCacheURLRequestJob::SetupRangeResponse() { 310 DCHECK(is_range_request() && info_.get() && reader_.get() && 311 is_delivering_appcache_response()); 312 int resource_size = static_cast<int>(info_->response_data_size()); 313 if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) { 314 range_requested_ = net::HttpByteRange(); 315 return; 316 } 317 318 DCHECK(range_requested_.IsValid()); 319 int offset = static_cast<int>(range_requested_.first_byte_position()); 320 int length = static_cast<int>(range_requested_.last_byte_position() - 321 range_requested_.first_byte_position() + 1); 322 323 // Tell the reader about the range to read. 324 reader_->SetReadRange(offset, length); 325 326 // Make a copy of the full response headers and fix them up 327 // for the range we'll be returning. 328 range_response_info_.reset( 329 new net::HttpResponseInfo(*info_->http_response_info())); 330 net::HttpResponseHeaders* headers = range_response_info_->headers.get(); 331 headers->UpdateWithNewRange( 332 range_requested_, resource_size, true /* replace status line */); 333 } 334 335 void AppCacheURLRequestJob::OnReadComplete(int result) { 336 DCHECK(is_delivering_appcache_response()); 337 if (result == 0) { 338 NotifyDone(net::URLRequestStatus()); 339 AppCacheHistograms::CountResponseRetrieval( 340 true, is_main_resource_, manifest_url_.GetOrigin()); 341 } else if (result < 0) { 342 if (storage_->service()->storage() == storage_) { 343 storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, 344 entry_.response_id()); 345 } 346 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); 347 AppCacheHistograms::CountResponseRetrieval( 348 false, is_main_resource_, manifest_url_.GetOrigin()); 349 } else { 350 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status 351 } 352 NotifyReadComplete(result); 353 } 354 355 // net::URLRequestJob overrides ------------------------------------------------ 356 357 void AppCacheURLRequestJob::Start() { 358 DCHECK(!has_been_started()); 359 has_been_started_ = true; 360 start_time_tick_ = base::TimeTicks::Now(); 361 MaybeBeginDelivery(); 362 } 363 364 void AppCacheURLRequestJob::Kill() { 365 if (!has_been_killed_) { 366 has_been_killed_ = true; 367 reader_.reset(); 368 handler_source_reader_.reset(); 369 if (storage_) { 370 storage_->CancelDelegateCallbacks(this); 371 storage_ = NULL; 372 } 373 host_ = NULL; 374 info_ = NULL; 375 cache_ = NULL; 376 group_ = NULL; 377 range_response_info_.reset(); 378 net::URLRequestJob::Kill(); 379 weak_factory_.InvalidateWeakPtrs(); 380 } 381 } 382 383 net::LoadState AppCacheURLRequestJob::GetLoadState() const { 384 if (!has_been_started()) 385 return net::LOAD_STATE_IDLE; 386 if (!has_delivery_orders()) 387 return net::LOAD_STATE_WAITING_FOR_APPCACHE; 388 if (delivery_type_ != APPCACHED_DELIVERY) 389 return net::LOAD_STATE_IDLE; 390 if (!info_.get()) 391 return net::LOAD_STATE_WAITING_FOR_APPCACHE; 392 if (reader_.get() && reader_->IsReadPending()) 393 return net::LOAD_STATE_READING_RESPONSE; 394 return net::LOAD_STATE_IDLE; 395 } 396 397 bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const { 398 if (!http_info()) 399 return false; 400 return http_info()->headers->GetMimeType(mime_type); 401 } 402 403 bool AppCacheURLRequestJob::GetCharset(std::string* charset) { 404 if (!http_info()) 405 return false; 406 return http_info()->headers->GetCharset(charset); 407 } 408 409 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { 410 if (!http_info()) 411 return; 412 *info = *http_info(); 413 } 414 415 int AppCacheURLRequestJob::GetResponseCode() const { 416 if (!http_info()) 417 return -1; 418 return http_info()->headers->response_code(); 419 } 420 421 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size, 422 int *bytes_read) { 423 DCHECK(is_delivering_appcache_response()); 424 DCHECK_NE(buf_size, 0); 425 DCHECK(bytes_read); 426 DCHECK(!reader_->IsReadPending()); 427 reader_->ReadData( 428 buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete, 429 base::Unretained(this))); 430 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); 431 return false; 432 } 433 434 void AppCacheURLRequestJob::SetExtraRequestHeaders( 435 const net::HttpRequestHeaders& headers) { 436 std::string value; 437 std::vector<net::HttpByteRange> ranges; 438 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) || 439 !net::HttpUtil::ParseRangeHeader(value, &ranges)) { 440 return; 441 } 442 443 // If multiple ranges are requested, we play dumb and 444 // return the entire response with 200 OK. 445 if (ranges.size() == 1U) 446 range_requested_ = ranges[0]; 447 } 448 449 } // namespace content 450