1// Copyright 2014 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/service_worker/service_worker_url_request_job.h"
6
7#include <map>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/guid.h"
13#include "base/strings/stringprintf.h"
14#include "base/time/time.h"
15#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
16#include "content/browser/service_worker/service_worker_provider_host.h"
17#include "content/common/resource_request_body.h"
18#include "content/common/service_worker/service_worker_types.h"
19#include "content/public/browser/blob_handle.h"
20#include "content/public/browser/resource_request_info.h"
21#include "net/base/net_errors.h"
22#include "net/http/http_request_headers.h"
23#include "net/http/http_response_headers.h"
24#include "net/http/http_response_info.h"
25#include "net/http/http_util.h"
26#include "storage/browser/blob/blob_data_handle.h"
27#include "storage/browser/blob/blob_storage_context.h"
28#include "storage/browser/blob/blob_url_request_job_factory.h"
29#include "ui/base/page_transition_types.h"
30
31namespace content {
32
33ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
34    net::URLRequest* request,
35    net::NetworkDelegate* network_delegate,
36    base::WeakPtr<ServiceWorkerProviderHost> provider_host,
37    base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
38    scoped_refptr<ResourceRequestBody> body)
39    : net::URLRequestJob(request, network_delegate),
40      provider_host_(provider_host),
41      response_type_(NOT_DETERMINED),
42      is_started_(false),
43      blob_storage_context_(blob_storage_context),
44      body_(body),
45      weak_factory_(this) {
46}
47
48void ServiceWorkerURLRequestJob::FallbackToNetwork() {
49  DCHECK_EQ(NOT_DETERMINED, response_type_);
50  response_type_ = FALLBACK_TO_NETWORK;
51  MaybeStartRequest();
52}
53
54void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
55  DCHECK_EQ(NOT_DETERMINED, response_type_);
56  response_type_ = FORWARD_TO_SERVICE_WORKER;
57  MaybeStartRequest();
58}
59
60void ServiceWorkerURLRequestJob::Start() {
61  is_started_ = true;
62  MaybeStartRequest();
63}
64
65void ServiceWorkerURLRequestJob::Kill() {
66  net::URLRequestJob::Kill();
67  fetch_dispatcher_.reset();
68  blob_request_.reset();
69  weak_factory_.InvalidateWeakPtrs();
70}
71
72net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
73  // TODO(kinuko): refine this for better debug.
74  return net::URLRequestJob::GetLoadState();
75}
76
77bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
78  if (!http_info())
79    return false;
80  return http_info()->headers->GetCharset(charset);
81}
82
83bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
84  if (!http_info())
85    return false;
86  return http_info()->headers->GetMimeType(mime_type);
87}
88
89void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
90  if (!http_info())
91    return;
92  *info = *http_info();
93  info->response_time = response_time_;
94}
95
96void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
97    net::LoadTimingInfo* load_timing_info) const {
98  *load_timing_info = load_timing_info_;
99}
100
101int ServiceWorkerURLRequestJob::GetResponseCode() const {
102  if (!http_info())
103    return -1;
104  return http_info()->headers->response_code();
105}
106
107void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
108    const net::HttpRequestHeaders& headers) {
109  std::string range_header;
110  std::vector<net::HttpByteRange> ranges;
111  if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
112      !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
113    return;
114  }
115
116  // We don't support multiple range requests in one single URL request.
117  if (ranges.size() == 1U)
118    byte_range_ = ranges[0];
119}
120
121bool ServiceWorkerURLRequestJob::ReadRawData(
122    net::IOBuffer* buf, int buf_size, int *bytes_read) {
123  if (!blob_request_) {
124    *bytes_read = 0;
125    return true;
126  }
127
128  blob_request_->Read(buf, buf_size, bytes_read);
129  net::URLRequestStatus status = blob_request_->status();
130  SetStatus(status);
131  if (status.is_io_pending())
132    return false;
133  return status.is_success();
134}
135
136void ServiceWorkerURLRequestJob::OnReceivedRedirect(
137    net::URLRequest* request,
138    const net::RedirectInfo& redirect_info,
139    bool* defer_redirect) {
140  NOTREACHED();
141}
142
143void ServiceWorkerURLRequestJob::OnAuthRequired(
144    net::URLRequest* request,
145    net::AuthChallengeInfo* auth_info) {
146  NOTREACHED();
147}
148
149void ServiceWorkerURLRequestJob::OnCertificateRequested(
150    net::URLRequest* request,
151    net::SSLCertRequestInfo* cert_request_info) {
152  NOTREACHED();
153}
154
155void ServiceWorkerURLRequestJob::OnSSLCertificateError(
156    net::URLRequest* request,
157    const net::SSLInfo& ssl_info,
158    bool fatal) {
159  NOTREACHED();
160}
161
162void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
163                                                      bool* defer) {
164  NOTREACHED();
165}
166
167void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
168  // TODO(falken): Add Content-Length, Content-Type if they were not provided in
169  // the ServiceWorkerResponse.
170  response_time_ = base::Time::Now();
171  CommitResponseHeader();
172}
173
174void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
175                                                 int bytes_read) {
176  SetStatus(request->status());
177  if (!request->status().is_success()) {
178    NotifyDone(request->status());
179    return;
180  }
181  NotifyReadComplete(bytes_read);
182  if (bytes_read == 0)
183    NotifyDone(request->status());
184}
185
186const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
187  if (!http_response_info_)
188    return NULL;
189  if (range_response_info_)
190    return range_response_info_.get();
191  return http_response_info_.get();
192}
193
194void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
195    bool* was_fetched_via_service_worker,
196    GURL* original_url_via_service_worker,
197    base::TimeTicks* fetch_start_time,
198    base::TimeTicks* fetch_ready_time,
199    base::TimeTicks* fetch_end_time) const {
200  if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
201    *was_fetched_via_service_worker = false;
202    *original_url_via_service_worker = GURL();
203    return;
204  }
205  *was_fetched_via_service_worker = true;
206  *original_url_via_service_worker = response_url_;
207  *fetch_start_time = fetch_start_time_;
208  *fetch_ready_time = fetch_ready_time_;
209  *fetch_end_time = fetch_end_time_;
210}
211
212
213ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
214}
215
216void ServiceWorkerURLRequestJob::MaybeStartRequest() {
217  if (is_started_ && response_type_ != NOT_DETERMINED) {
218    // Start asynchronously.
219    base::MessageLoop::current()->PostTask(
220        FROM_HERE,
221        base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
222                   weak_factory_.GetWeakPtr()));
223  }
224}
225
226void ServiceWorkerURLRequestJob::StartRequest() {
227  switch (response_type_) {
228    case NOT_DETERMINED:
229      NOTREACHED();
230      return;
231
232    case FALLBACK_TO_NETWORK:
233      // Restart the request to create a new job. Our request handler will
234      // return NULL, and the default job (which will hit network) should be
235      // created.
236      NotifyRestartRequired();
237      return;
238
239    case FORWARD_TO_SERVICE_WORKER:
240      DCHECK(provider_host_ && provider_host_->active_version());
241      DCHECK(!fetch_dispatcher_);
242      // Send a fetch event to the ServiceWorker associated to the
243      // provider_host.
244      fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
245          CreateFetchRequest(),
246          provider_host_->active_version(),
247          base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
248                     weak_factory_.GetWeakPtr()),
249          base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
250                     weak_factory_.GetWeakPtr())));
251      fetch_start_time_ = base::TimeTicks::Now();
252      load_timing_info_.send_start = fetch_start_time_;
253      fetch_dispatcher_->Run();
254      return;
255  }
256
257  NOTREACHED();
258}
259
260scoped_ptr<ServiceWorkerFetchRequest>
261ServiceWorkerURLRequestJob::CreateFetchRequest() {
262  std::string blob_uuid;
263  uint64 blob_size = 0;
264  CreateRequestBodyBlob(&blob_uuid, &blob_size);
265  scoped_ptr<ServiceWorkerFetchRequest> request(
266      new ServiceWorkerFetchRequest());
267
268  request->url = request_->url();
269  request->method = request_->method();
270  const net::HttpRequestHeaders& headers = request_->extra_request_headers();
271  for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();)
272    request->headers[it.name()] = it.value();
273  request->blob_uuid = blob_uuid;
274  request->blob_size = blob_size;
275  request->referrer = GURL(request_->referrer());
276  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
277  if (info) {
278    request->is_reload = ui::PageTransitionCoreTypeIs(
279        info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
280  }
281  return request.Pass();
282}
283
284bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
285                                                       uint64* blob_size) {
286  if (!body_.get() || !blob_storage_context_)
287    return false;
288
289  std::vector<const ResourceRequestBody::Element*> resolved_elements;
290  for (size_t i = 0; i < body_->elements()->size(); ++i) {
291    const ResourceRequestBody::Element& element = (*body_->elements())[i];
292    if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) {
293      resolved_elements.push_back(&element);
294      continue;
295    }
296    scoped_ptr<storage::BlobDataHandle> handle =
297        blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid());
298    if (handle->data()->items().empty())
299      continue;
300    for (size_t i = 0; i < handle->data()->items().size(); ++i) {
301      const storage::BlobData::Item& item = handle->data()->items().at(i);
302      DCHECK_NE(storage::BlobData::Item::TYPE_BLOB, item.type());
303      resolved_elements.push_back(&item);
304    }
305  }
306
307  const std::string uuid(base::GenerateGUID());
308  uint64 total_size = 0;
309  scoped_refptr<storage::BlobData> blob_data = new storage::BlobData(uuid);
310  for (size_t i = 0; i < resolved_elements.size(); ++i) {
311    const ResourceRequestBody::Element& element = *resolved_elements[i];
312    if (total_size != kuint64max && element.length() != kuint64max)
313      total_size += element.length();
314    else
315      total_size = kuint64max;
316    switch (element.type()) {
317      case ResourceRequestBody::Element::TYPE_BYTES:
318        blob_data->AppendData(element.bytes(), element.length());
319        break;
320      case ResourceRequestBody::Element::TYPE_FILE:
321        blob_data->AppendFile(element.path(),
322                              element.offset(),
323                              element.length(),
324                              element.expected_modification_time());
325        break;
326      case ResourceRequestBody::Element::TYPE_BLOB:
327        // Blob elements should be resolved beforehand.
328        NOTREACHED();
329        break;
330      case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
331        blob_data->AppendFileSystemFile(element.filesystem_url(),
332                                        element.offset(),
333                                        element.length(),
334                                        element.expected_modification_time());
335        break;
336      default:
337        NOTIMPLEMENTED();
338    }
339  }
340
341  request_body_blob_data_handle_ =
342      blob_storage_context_->AddFinishedBlob(blob_data.get());
343  *blob_uuid = uuid;
344  *blob_size = total_size;
345  return true;
346}
347
348void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
349  fetch_ready_time_ = base::TimeTicks::Now();
350}
351
352void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
353    ServiceWorkerStatusCode status,
354    ServiceWorkerFetchEventResult fetch_result,
355    const ServiceWorkerResponse& response) {
356  fetch_dispatcher_.reset();
357
358  // Check if we're not orphaned.
359  if (!request())
360    return;
361
362  if (status != SERVICE_WORKER_OK) {
363    // Dispatching event has been failed, falling back to the network.
364    // (Tentative behavior described on github)
365    // TODO(kinuko): consider returning error if we've come here because
366    // unexpected worker termination etc (so that we could fix bugs).
367    // TODO(kinuko): Would be nice to log the error case.
368    response_type_ = FALLBACK_TO_NETWORK;
369    NotifyRestartRequired();
370    return;
371  }
372
373  if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
374    // Change the response type and restart the request to fallback to
375    // the network.
376    response_type_ = FALLBACK_TO_NETWORK;
377    NotifyRestartRequired();
378    return;
379  }
380
381  // We should have a response now.
382  DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
383
384  // Treat a response whose status is 0 as a Network Error.
385  if (response.status_code == 0) {
386    NotifyDone(
387        net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
388    return;
389  }
390
391  fetch_end_time_ = base::TimeTicks::Now();
392  load_timing_info_.send_end = fetch_end_time_;
393
394  // Set up a request for reading the blob.
395  if (!response.blob_uuid.empty() && blob_storage_context_) {
396    scoped_ptr<storage::BlobDataHandle> blob_data_handle =
397        blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
398    if (!blob_data_handle) {
399      // The renderer gave us a bad blob UUID.
400      DeliverErrorResponse();
401      return;
402    }
403    blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
404        blob_data_handle.Pass(), request()->context(), this);
405    blob_request_->Start();
406  }
407
408  response_url_ = response.url;
409  CreateResponseHeader(
410      response.status_code, response.status_text, response.headers);
411  load_timing_info_.receive_headers_end = base::TimeTicks::Now();
412  if (!blob_request_)
413    CommitResponseHeader();
414}
415
416void ServiceWorkerURLRequestJob::CreateResponseHeader(
417    int status_code,
418    const std::string& status_text,
419    const ServiceWorkerHeaderMap& headers) {
420  // TODO(kinuko): If the response has an identifier to on-disk cache entry,
421  // pull response header from the disk.
422  std::string status_line(
423      base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
424  status_line.push_back('\0');
425  http_response_headers_ = new net::HttpResponseHeaders(status_line);
426  for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
427       it != headers.end();
428       ++it) {
429    std::string header;
430    header.reserve(it->first.size() + 2 + it->second.size());
431    header.append(it->first);
432    header.append(": ");
433    header.append(it->second);
434    http_response_headers_->AddHeader(header);
435  }
436}
437
438void ServiceWorkerURLRequestJob::CommitResponseHeader() {
439  http_response_info_.reset(new net::HttpResponseInfo());
440  http_response_info_->headers.swap(http_response_headers_);
441  NotifyHeadersComplete();
442}
443
444void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
445  // TODO(falken): Print an error to the console of the ServiceWorker and of
446  // the requesting page.
447  CreateResponseHeader(
448      500, "Service Worker Response Error", ServiceWorkerHeaderMap());
449  CommitResponseHeader();
450}
451
452}  // namespace content
453