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
30namespace content {
31
32AppCacheURLRequestJob::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
50void 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
64void AppCacheURLRequestJob::DeliverNetworkResponse() {
65  DCHECK(!has_delivery_orders());
66  delivery_type_ = NETWORK_DELIVERY;
67  storage_ = NULL;  // not needed
68  MaybeBeginDelivery();
69}
70
71void AppCacheURLRequestJob::DeliverErrorResponse() {
72  DCHECK(!has_delivery_orders());
73  delivery_type_ = ERROR_DELIVERY;
74  storage_ = NULL;  // not needed
75  MaybeBeginDelivery();
76}
77
78void 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
89void 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
136void 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
158void 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
199void 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
220void AppCacheURLRequestJob::InvokeExecutableHandler(
221    AppCacheExecutableHandler* handler) {
222  handler->HandleRequest(
223      request(),
224      base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
225                 weak_factory_.GetWeakPtr()));
226}
227
228void 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
257void 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
266AppCacheURLRequestJob::~AppCacheURLRequestJob() {
267  if (storage_)
268    storage_->CancelDelegateCallbacks(this);
269}
270
271void 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
301const 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
309void 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
335void 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
357void AppCacheURLRequestJob::Start() {
358  DCHECK(!has_been_started());
359  has_been_started_ = true;
360  start_time_tick_ = base::TimeTicks::Now();
361  MaybeBeginDelivery();
362}
363
364void 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
383net::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
397bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
398  if (!http_info())
399    return false;
400  return http_info()->headers->GetMimeType(mime_type);
401}
402
403bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
404  if (!http_info())
405    return false;
406  return http_info()->headers->GetCharset(charset);
407}
408
409void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
410  if (!http_info())
411    return;
412  *info = *http_info();
413}
414
415int AppCacheURLRequestJob::GetResponseCode() const {
416  if (!http_info())
417    return -1;
418  return http_info()->headers->response_code();
419}
420
421bool 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
434void 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