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