• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "webkit/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 "net/base/io_buffer.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/net_log.h"
19 #include "net/http/http_request_headers.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_util.h"
22 #include "net/url_request/url_request.h"
23 #include "net/url_request/url_request_status.h"
24 #include "webkit/browser/appcache/appcache.h"
25 #include "webkit/browser/appcache/appcache_group.h"
26 #include "webkit/browser/appcache/appcache_histograms.h"
27 #include "webkit/browser/appcache/appcache_host.h"
28 #include "webkit/browser/appcache/appcache_service_impl.h"
29 
30 namespace appcache {
31 
AppCacheURLRequestJob(net::URLRequest * request,net::NetworkDelegate * network_delegate,AppCacheStorage * storage,AppCacheHost * host,bool is_main_resource)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 
DeliverAppCachedResponse(const GURL & manifest_url,int64 group_id,int64 cache_id,const AppCacheEntry & entry,bool is_fallback)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 
DeliverNetworkResponse()64 void AppCacheURLRequestJob::DeliverNetworkResponse() {
65   DCHECK(!has_delivery_orders());
66   delivery_type_ = NETWORK_DELIVERY;
67   storage_ = NULL;  // not needed
68   MaybeBeginDelivery();
69 }
70 
DeliverErrorResponse()71 void AppCacheURLRequestJob::DeliverErrorResponse() {
72   DCHECK(!has_delivery_orders());
73   delivery_type_ = ERROR_DELIVERY;
74   storage_ = NULL;  // not needed
75   MaybeBeginDelivery();
76 }
77 
MaybeBeginDelivery()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 
BeginDelivery()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 
BeginExecutableHandlerDelivery()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 
OnCacheLoaded(AppCache * cache,int64 cache_id)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 
OnExecutableSourceLoaded(int result)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 
InvokeExecutableHandler(AppCacheExecutableHandler * handler)220 void AppCacheURLRequestJob::InvokeExecutableHandler(
221     AppCacheExecutableHandler* handler) {
222   handler->HandleRequest(
223       request(),
224       base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
225                  weak_factory_.GetWeakPtr()));
226 }
227 
OnExecutableResponseCallback(const AppCacheExecutableHandler::Response & response)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 
BeginErrorDelivery(const char * message)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 
~AppCacheURLRequestJob()266 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
267   if (storage_)
268     storage_->CancelDelegateCallbacks(this);
269 }
270 
OnResponseInfoLoaded(AppCacheResponseInfo * response_info,int64 response_id)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 
http_info() const301 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 
SetupRangeResponse()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 
OnReadComplete(int result)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 
Start()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 
Kill()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 
GetLoadState() const383 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 
GetMimeType(std::string * mime_type) const397 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 
GetCharset(std::string * charset)403 bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
404   if (!http_info())
405     return false;
406   return http_info()->headers->GetCharset(charset);
407 }
408 
GetResponseInfo(net::HttpResponseInfo * info)409 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
410   if (!http_info())
411     return;
412   *info = *http_info();
413 }
414 
GetResponseCode() const415 int AppCacheURLRequestJob::GetResponseCode() const {
416   if (!http_info())
417     return -1;
418   return http_info()->headers->response_code();
419 }
420 
ReadRawData(net::IOBuffer * buf,int buf_size,int * bytes_read)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 
SetExtraRequestHeaders(const net::HttpRequestHeaders & headers)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 appcache
450