// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_url_request_job.h"

#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/streams/stream.h"
#include "content/browser/streams/stream_context.h"
#include "content/browser/streams/stream_registry.h"
#include "content/common/resource_request_body.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/common/referrer.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_util.h"
#include "net/log/net_log.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/blob/blob_url_request_job_factory.h"
#include "ui/base/page_transition_types.h"

namespace content {

namespace {

net::NetLog::EventType RequestJobResultToNetEventType(
    ServiceWorkerMetrics::URLRequestJobResult result) {
  using n = net::NetLog;
  using m = ServiceWorkerMetrics;
  switch (result) {
    case m::REQUEST_JOB_FALLBACK_RESPONSE:
      return n::TYPE_SERVICE_WORKER_FALLBACK_RESPONSE;
    case m::REQUEST_JOB_FALLBACK_FOR_CORS:
      return n::TYPE_SERVICE_WORKER_FALLBACK_FOR_CORS;
    case m::REQUEST_JOB_HEADERS_ONLY_RESPONSE:
      return n::TYPE_SERVICE_WORKER_HEADERS_ONLY_RESPONSE;
    case m::REQUEST_JOB_STREAM_RESPONSE:
      return n::TYPE_SERVICE_WORKER_STREAM_RESPONSE;
    case m::REQUEST_JOB_BLOB_RESPONSE:
      return n::TYPE_SERVICE_WORKER_BLOB_RESPONSE;
    case m::REQUEST_JOB_ERROR_RESPONSE_STATUS_ZERO:
      return n::TYPE_SERVICE_WORKER_ERROR_RESPONSE_STATUS_ZERO;
    case m::REQUEST_JOB_ERROR_BAD_BLOB:
      return n::TYPE_SERVICE_WORKER_ERROR_BAD_BLOB;
    case m::REQUEST_JOB_ERROR_NO_PROVIDER_HOST:
      return n::TYPE_SERVICE_WORKER_ERROR_NO_PROVIDER_HOST;
    case m::REQUEST_JOB_ERROR_NO_ACTIVE_VERSION:
      return n::TYPE_SERVICE_WORKER_ERROR_NO_ACTIVE_VERSION;
    case m::REQUEST_JOB_ERROR_FETCH_EVENT_DISPATCH:
      return n::TYPE_SERVICE_WORKER_ERROR_FETCH_EVENT_DISPATCH;
    case m::REQUEST_JOB_ERROR_BLOB_READ:
      return n::TYPE_SERVICE_WORKER_ERROR_BLOB_READ;
    case m::REQUEST_JOB_ERROR_STREAM_ABORTED:
      return n::TYPE_SERVICE_WORKER_ERROR_STREAM_ABORTED;
    case m::REQUEST_JOB_ERROR_KILLED:
      return n::TYPE_SERVICE_WORKER_ERROR_KILLED;
    case m::REQUEST_JOB_ERROR_KILLED_WITH_BLOB:
      return n::TYPE_SERVICE_WORKER_ERROR_KILLED_WITH_BLOB;
    case m::REQUEST_JOB_ERROR_KILLED_WITH_STREAM:
      return n::TYPE_SERVICE_WORKER_ERROR_KILLED_WITH_STREAM;
    case m::REQUEST_JOB_ERROR_BAD_DELEGATE:
      return n::TYPE_SERVICE_WORKER_ERROR_BAD_DELEGATE;
    // We can't log if there's no request; fallthrough.
    case m::REQUEST_JOB_ERROR_NO_REQUEST:
    // Obsolete types; fallthrough.
    case m::REQUEST_JOB_ERROR_DESTROYED:
    case m::REQUEST_JOB_ERROR_DESTROYED_WITH_BLOB:
    case m::REQUEST_JOB_ERROR_DESTROYED_WITH_STREAM:
    // Invalid type.
    case m::NUM_REQUEST_JOB_RESULT_TYPES:
      NOTREACHED() << result;
  }
  NOTREACHED() << result;
  return n::TYPE_FAILED;
}

}  // namespace

bool ServiceWorkerURLRequestJob::Delegate::RequestStillValid(
    ServiceWorkerMetrics::URLRequestJobResult* result) {
  return true;
}

ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
    net::URLRequest* request,
    net::NetworkDelegate* network_delegate,
    const std::string& client_id,
    base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
    const ResourceContext* resource_context,
    FetchRequestMode request_mode,
    FetchCredentialsMode credentials_mode,
    FetchRedirectMode redirect_mode,
    bool is_main_resource_load,
    RequestContextType request_context_type,
    RequestContextFrameType frame_type,
    scoped_refptr<ResourceRequestBody> body,
    Delegate* delegate)
    : net::URLRequestJob(request, network_delegate),
      delegate_(delegate),
      response_type_(NOT_DETERMINED),
      is_started_(false),
      service_worker_response_type_(blink::WebServiceWorkerResponseTypeDefault),
      client_id_(client_id),
      blob_storage_context_(blob_storage_context),
      resource_context_(resource_context),
      stream_pending_buffer_size_(0),
      request_mode_(request_mode),
      credentials_mode_(credentials_mode),
      redirect_mode_(redirect_mode),
      is_main_resource_load_(is_main_resource_load),
      request_context_type_(request_context_type),
      frame_type_(frame_type),
      fall_back_required_(false),
      body_(body),
      weak_factory_(this) {
  DCHECK(delegate_) << "ServiceWorkerURLRequestJob requires a delegate";
}

ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
  ClearStream();

  if (!ShouldRecordResult())
    return;
  ServiceWorkerMetrics::URLRequestJobResult result =
      ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED;
  if (response_body_type_ == STREAM)
    result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED_WITH_STREAM;
  else if (response_body_type_ == BLOB)
    result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED_WITH_BLOB;
  RecordResult(result);
}

void ServiceWorkerURLRequestJob::FallbackToNetwork() {
  DCHECK_EQ(NOT_DETERMINED, response_type_);
  response_type_ = FALLBACK_TO_NETWORK;
  MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
  DCHECK_EQ(NOT_DETERMINED, response_type_);
  response_type_ = FORWARD_TO_SERVICE_WORKER;
  MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::Start() {
  is_started_ = true;
  MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::Kill() {
  net::URLRequestJob::Kill();
  ClearStream();
  fetch_dispatcher_.reset();
  blob_request_.reset();
  weak_factory_.InvalidateWeakPtrs();
}

net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
  // TODO(kinuko): refine this for better debug.
  return net::URLRequestJob::GetLoadState();
}

bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
  if (!http_info())
    return false;
  return http_info()->headers->GetCharset(charset);
}

bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
  if (!http_info())
    return false;
  return http_info()->headers->GetMimeType(mime_type);
}

void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
  if (!http_info())
    return;
  const base::Time request_time = info->request_time;
  *info = *http_info();
  info->request_time = request_time;
  info->response_time = response_time_;
}

void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
    net::LoadTimingInfo* load_timing_info) const {
  *load_timing_info = load_timing_info_;
}

int ServiceWorkerURLRequestJob::GetResponseCode() const {
  if (!http_info())
    return -1;
  return http_info()->headers->response_code();
}

void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
    const net::HttpRequestHeaders& headers) {
  std::string range_header;
  std::vector<net::HttpByteRange> ranges;
  if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
      !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
    return;
  }

  // We don't support multiple range requests in one single URL request.
  if (ranges.size() == 1U)
    byte_range_ = ranges[0];
}

int ServiceWorkerURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size) {
  DCHECK(buf);
  DCHECK_GE(buf_size, 0);
  DCHECK(waiting_stream_url_.is_empty());

  int bytes_read = 0;

  if (stream_.get()) {
    switch (stream_->ReadRawData(buf, buf_size, &bytes_read)) {
      case Stream::STREAM_HAS_DATA:
        DCHECK_GT(bytes_read, 0);
        return bytes_read;
      case Stream::STREAM_COMPLETE:
        DCHECK_EQ(0, bytes_read);
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_STREAM_RESPONSE);
        return 0;
      case Stream::STREAM_EMPTY:
        stream_pending_buffer_ = buf;
        stream_pending_buffer_size_ = buf_size;
        return net::ERR_IO_PENDING;
      case Stream::STREAM_ABORTED:
        // Handle this as connection reset.
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_STREAM_ABORTED);
        return net::ERR_CONNECTION_RESET;
    }
    NOTREACHED();
    return net::ERR_FAILED;
  }

  if (!blob_request_)
    return 0;
  blob_request_->Read(buf, buf_size, &bytes_read);
  net::URLRequestStatus status = blob_request_->status();
  if (status.status() != net::URLRequestStatus::SUCCESS)
    return status.error();
  if (bytes_read == 0)
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_BLOB_RESPONSE);
  return bytes_read;
}

// TODO(falken): Refactor Blob and Stream specific handling to separate classes.
// Overrides for Blob reading -------------------------------------------------

void ServiceWorkerURLRequestJob::OnReceivedRedirect(
    net::URLRequest* request,
    const net::RedirectInfo& redirect_info,
    bool* defer_redirect) {
  NOTREACHED();
}

void ServiceWorkerURLRequestJob::OnAuthRequired(
    net::URLRequest* request,
    net::AuthChallengeInfo* auth_info) {
  NOTREACHED();
}

void ServiceWorkerURLRequestJob::OnCertificateRequested(
    net::URLRequest* request,
    net::SSLCertRequestInfo* cert_request_info) {
  NOTREACHED();
}

void ServiceWorkerURLRequestJob::OnSSLCertificateError(
    net::URLRequest* request,
    const net::SSLInfo& ssl_info,
    bool fatal) {
  NOTREACHED();
}

void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
                                                      bool* defer) {
  NOTREACHED();
}

void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
  // TODO(falken): Add Content-Length, Content-Type if they were not provided in
  // the ServiceWorkerResponse.
  response_time_ = base::Time::Now();
  CommitResponseHeader();
}

void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
                                                 int bytes_read) {
  if (!request->status().is_success()) {
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_BLOB_READ);
  } else if (bytes_read == 0) {
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_BLOB_RESPONSE);
  }
  net::URLRequestStatus status = request->status();
  ReadRawDataComplete(status.is_success() ? bytes_read : status.error());
}

// Overrides for Stream reading -----------------------------------------------

void ServiceWorkerURLRequestJob::OnDataAvailable(Stream* stream) {
  // Do nothing if stream_pending_buffer_ is empty, i.e. there's no ReadRawData
  // operation waiting for IO completion.
  if (!stream_pending_buffer_.get())
    return;

  // stream_pending_buffer_ is set to the IOBuffer instance provided to
  // ReadRawData() by URLRequestJob.

  int result = 0;
  switch (stream_->ReadRawData(stream_pending_buffer_.get(),
                               stream_pending_buffer_size_, &result)) {
    case Stream::STREAM_HAS_DATA:
      DCHECK_GT(result, 0);
      break;
    case Stream::STREAM_COMPLETE:
      // Calling NotifyReadComplete with 0 signals completion.
      DCHECK(!result);
      RecordResult(ServiceWorkerMetrics::REQUEST_JOB_STREAM_RESPONSE);
      break;
    case Stream::STREAM_EMPTY:
      NOTREACHED();
      break;
    case Stream::STREAM_ABORTED:
      // Handle this as connection reset.
      result = net::ERR_CONNECTION_RESET;
      RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_STREAM_ABORTED);
      break;
  }

  // Clear the buffers before notifying the read is complete, so that it is
  // safe for the observer to read.
  stream_pending_buffer_ = nullptr;
  stream_pending_buffer_size_ = 0;
  ReadRawDataComplete(result);
}

void ServiceWorkerURLRequestJob::OnStreamRegistered(Stream* stream) {
  StreamContext* stream_context =
      GetStreamContextForResourceContext(resource_context_);
  stream_context->registry()->RemoveRegisterObserver(waiting_stream_url_);
  waiting_stream_url_ = GURL();
  stream_ = stream;
  stream_->SetReadObserver(this);
  CommitResponseHeader();
}

base::WeakPtr<ServiceWorkerURLRequestJob>
ServiceWorkerURLRequestJob::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

// Misc -----------------------------------------------------------------------

const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
  if (!http_response_info_)
    return nullptr;
  if (range_response_info_)
    return range_response_info_.get();
  return http_response_info_.get();
}

void ServiceWorkerURLRequestJob::MaybeStartRequest() {
  if (is_started_ && response_type_ != NOT_DETERMINED) {
    // Start asynchronously.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
                              weak_factory_.GetWeakPtr()));
  }
}

void ServiceWorkerURLRequestJob::StartRequest() {
  if (request()) {
    request()->net_log().AddEvent(
        net::NetLog::TYPE_SERVICE_WORKER_START_REQUEST);
  }

  switch (response_type_) {
    case NOT_DETERMINED:
      NOTREACHED();
      return;

    case FALLBACK_TO_NETWORK:
      // Restart the request to create a new job. Our request handler will
      // return nullptr, and the default job (which will hit network) should be
      // created.
      NotifyRestartRequired();
      return;

    case FORWARD_TO_SERVICE_WORKER:
      ServiceWorkerMetrics::URLRequestJobResult result =
          ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_DELEGATE;
      ServiceWorkerVersion* active_worker =
          delegate_->GetServiceWorkerVersion(&result);
      if (!active_worker) {
        RecordResult(result);
        DeliverErrorResponse();
        return;
      }

      DCHECK(!fetch_dispatcher_);
      // Send a fetch event to the ServiceWorker associated to the
      // provider_host.
      fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
          CreateFetchRequest(), active_worker,
          base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
                     weak_factory_.GetWeakPtr()),
          base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
                     weak_factory_.GetWeakPtr())));
      worker_start_time_ = base::TimeTicks::Now();
      fetch_dispatcher_->Run();
      return;
  }

  NOTREACHED();
}

scoped_ptr<ServiceWorkerFetchRequest>
ServiceWorkerURLRequestJob::CreateFetchRequest() {
  std::string blob_uuid;
  uint64_t blob_size = 0;
  CreateRequestBodyBlob(&blob_uuid, &blob_size);
  scoped_ptr<ServiceWorkerFetchRequest> request(
      new ServiceWorkerFetchRequest());
  request->mode = request_mode_;
  request->is_main_resource_load = is_main_resource_load_;
  request->request_context_type = request_context_type_;
  request->frame_type = frame_type_;
  request->url = request_->url();
  request->method = request_->method();
  const net::HttpRequestHeaders& headers = request_->extra_request_headers();
  for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) {
    if (ServiceWorkerContext::IsExcludedHeaderNameForFetchEvent(it.name()))
      continue;
    request->headers[it.name()] = it.value();
  }
  request->blob_uuid = blob_uuid;
  request->blob_size = blob_size;
  request->credentials_mode = credentials_mode_;
  request->redirect_mode = redirect_mode_;
  request->client_id = client_id_;
  const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
  if (info) {
    request->is_reload = ui::PageTransitionCoreTypeIs(
        info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
    request->referrer =
        Referrer(GURL(request_->referrer()), info->GetReferrerPolicy());
  } else {
    CHECK(
        request_->referrer_policy() ==
        net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE);
    request->referrer =
        Referrer(GURL(request_->referrer()), blink::WebReferrerPolicyDefault);
  }
  return request;
}

bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
                                                       uint64_t* blob_size) {
  if (!body_.get() || !blob_storage_context_)
    return false;

  // To ensure the blobs stick around until the end of the reading.
  std::vector<scoped_ptr<storage::BlobDataHandle>> handles;
  std::vector<scoped_ptr<storage::BlobDataSnapshot>> snapshots;
  // TODO(dmurph): Allow blobs to be added below, so that the context can
  // efficiently re-use blob items for the new blob.
  std::vector<const ResourceRequestBody::Element*> resolved_elements;
  for (const ResourceRequestBody::Element& element : (*body_->elements())) {
    if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) {
      resolved_elements.push_back(&element);
      continue;
    }
    scoped_ptr<storage::BlobDataHandle> handle =
        blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid());
    scoped_ptr<storage::BlobDataSnapshot> snapshot = handle->CreateSnapshot();
    if (snapshot->items().empty())
      continue;
    const auto& items = snapshot->items();
    for (const auto& item : items) {
      DCHECK_NE(storage::DataElement::TYPE_BLOB, item->type());
      resolved_elements.push_back(item->data_element_ptr());
    }
    handles.push_back(std::move(handle));
    snapshots.push_back(std::move(snapshot));
  }

  const std::string uuid(base::GenerateGUID());
  uint64_t total_size = 0;

  storage::BlobDataBuilder blob_builder(uuid);
  for (size_t i = 0; i < resolved_elements.size(); ++i) {
    const ResourceRequestBody::Element& element = *resolved_elements[i];
    if (total_size != std::numeric_limits<uint64_t>::max() &&
        element.length() != std::numeric_limits<uint64_t>::max())
      total_size += element.length();
    else
      total_size = std::numeric_limits<uint64_t>::max();
    switch (element.type()) {
      case ResourceRequestBody::Element::TYPE_BYTES:
        blob_builder.AppendData(element.bytes(), element.length());
        break;
      case ResourceRequestBody::Element::TYPE_FILE:
        blob_builder.AppendFile(element.path(), element.offset(),
                                element.length(),
                                element.expected_modification_time());
        break;
      case ResourceRequestBody::Element::TYPE_BLOB:
        // Blob elements should be resolved beforehand.
        NOTREACHED();
        break;
      case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
        blob_builder.AppendFileSystemFile(element.filesystem_url(),
                                          element.offset(), element.length(),
                                          element.expected_modification_time());
        break;
      default:
        NOTIMPLEMENTED();
    }
  }

  request_body_blob_data_handle_ =
      blob_storage_context_->AddFinishedBlob(&blob_builder);
  *blob_uuid = uuid;
  *blob_size = total_size;
  return true;
}

void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
  worker_ready_time_ = base::TimeTicks::Now();
  load_timing_info_.send_start = worker_ready_time_;
}

void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
    ServiceWorkerStatusCode status,
    ServiceWorkerFetchEventResult fetch_result,
    const ServiceWorkerResponse& response,
    scoped_refptr<ServiceWorkerVersion> version) {
  fetch_dispatcher_.reset();
  ServiceWorkerMetrics::RecordFetchEventStatus(is_main_resource_load_, status);

  // Check if we're not orphaned.
  if (!request()) {
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_NO_REQUEST);
    return;
  }

  ServiceWorkerMetrics::URLRequestJobResult result =
      ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_DELEGATE;
  if (!delegate_->RequestStillValid(&result)) {
    RecordResult(result);
    DeliverErrorResponse();
    return;
  }

  if (status != SERVICE_WORKER_OK) {
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_FETCH_EVENT_DISPATCH);
    if (is_main_resource_load_) {
      // Using the service worker failed, so fallback to network.
      delegate_->MainResourceLoadFailed();
      response_type_ = FALLBACK_TO_NETWORK;
      NotifyRestartRequired();
    } else {
      DeliverErrorResponse();
    }
    return;
  }

  if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
    ServiceWorkerMetrics::RecordFallbackedRequestMode(request_mode_);
    // When the request_mode is |CORS| or |CORS-with-forced-preflight| and the
    // origin of the request URL is different from the security origin of the
    // document, we can't simply fallback to the network in the browser process.
    // It is because the CORS preflight logic is implemented in the renderer. So
    // we returns a fall_back_required response to the renderer.
    if ((request_mode_ == FETCH_REQUEST_MODE_CORS ||
         request_mode_ == FETCH_REQUEST_MODE_CORS_WITH_FORCED_PREFLIGHT) &&
        delegate_->GetRequestingOrigin() != request()->url().GetOrigin()) {
      fall_back_required_ = true;
      RecordResult(ServiceWorkerMetrics::REQUEST_JOB_FALLBACK_FOR_CORS);
      CreateResponseHeader(
          400, "Service Worker Fallback Required", ServiceWorkerHeaderMap());
      CommitResponseHeader();
      return;
    }
    // Change the response type and restart the request to fallback to
    // the network.
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_FALLBACK_RESPONSE);
    response_type_ = FALLBACK_TO_NETWORK;
    NotifyRestartRequired();
    return;
  }

  // We should have a response now.
  DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);

  // A response with status code 0 is Blink telling us to respond with network
  // error.
  if (response.status_code == 0) {
    RecordStatusZeroResponseError(response.error);
    NotifyStartError(
        net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
    return;
  }

  load_timing_info_.send_end = base::TimeTicks::Now();

  // Creates a new HttpResponseInfo using the the ServiceWorker script's
  // HttpResponseInfo to show HTTPS padlock.
  // TODO(horo): When we support mixed-content (HTTP) no-cors requests from a
  // ServiceWorker, we have to check the security level of the responses.
  DCHECK(!http_response_info_);
  DCHECK(version);
  const net::HttpResponseInfo* main_script_http_info =
      version->GetMainScriptHttpResponseInfo();
  if (main_script_http_info) {
    // In normal case |main_script_http_info| must be set while starting the
    // ServiceWorker. But when the ServiceWorker registration database was not
    // written correctly, it may be null.
    // TODO(horo): Change this line to DCHECK when crbug.com/485900 is fixed.
    http_response_info_.reset(
        new net::HttpResponseInfo(*main_script_http_info));
  }

  // Set up a request for reading the stream.
  if (response.stream_url.is_valid()) {
    DCHECK(response.blob_uuid.empty());
    SetResponseBodyType(STREAM);
    streaming_version_ = version;
    streaming_version_->AddStreamingURLRequestJob(this);
    response_url_ = response.url;
    service_worker_response_type_ = response.response_type;
    CreateResponseHeader(
        response.status_code, response.status_text, response.headers);
    load_timing_info_.receive_headers_end = base::TimeTicks::Now();
    StreamContext* stream_context =
        GetStreamContextForResourceContext(resource_context_);
    stream_ =
        stream_context->registry()->GetStream(response.stream_url);
    if (!stream_.get()) {
      waiting_stream_url_ = response.stream_url;
      // Wait for StreamHostMsg_StartBuilding message from the ServiceWorker.
      stream_context->registry()->SetRegisterObserver(waiting_stream_url_,
                                                      this);
      return;
    }
    stream_->SetReadObserver(this);
    CommitResponseHeader();
    return;
  }

  // Set up a request for reading the blob.
  if (!response.blob_uuid.empty() && blob_storage_context_) {
    SetResponseBodyType(BLOB);
    scoped_ptr<storage::BlobDataHandle> blob_data_handle =
        blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
    if (!blob_data_handle) {
      // The renderer gave us a bad blob UUID.
      RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_BLOB);
      DeliverErrorResponse();
      return;
    }
    blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
        std::move(blob_data_handle), request()->context(), this);
    blob_request_->Start();
  }

  response_url_ = response.url;
  service_worker_response_type_ = response.response_type;
  CreateResponseHeader(
      response.status_code, response.status_text, response.headers);
  load_timing_info_.receive_headers_end = base::TimeTicks::Now();
  if (!blob_request_) {
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_HEADERS_ONLY_RESPONSE);
    CommitResponseHeader();
  }
}

void ServiceWorkerURLRequestJob::CreateResponseHeader(
    int status_code,
    const std::string& status_text,
    const ServiceWorkerHeaderMap& headers) {
  // TODO(kinuko): If the response has an identifier to on-disk cache entry,
  // pull response header from the disk.
  std::string status_line(
      base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
  status_line.push_back('\0');
  http_response_headers_ = new net::HttpResponseHeaders(status_line);
  for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
       it != headers.end();
       ++it) {
    std::string header;
    header.reserve(it->first.size() + 2 + it->second.size());
    header.append(it->first);
    header.append(": ");
    header.append(it->second);
    http_response_headers_->AddHeader(header);
  }
}

void ServiceWorkerURLRequestJob::CommitResponseHeader() {
  if (!http_response_info_)
    http_response_info_.reset(new net::HttpResponseInfo());
  http_response_info_->headers.swap(http_response_headers_);
  http_response_info_->vary_data = net::HttpVaryData();
  http_response_info_->metadata = nullptr;
  NotifyHeadersComplete();
}

void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
  // TODO(falken): Print an error to the console of the ServiceWorker and of
  // the requesting page.
  CreateResponseHeader(
      500, "Service Worker Response Error", ServiceWorkerHeaderMap());
  CommitResponseHeader();
}

void ServiceWorkerURLRequestJob::SetResponseBodyType(ResponseBodyType type) {
  DCHECK_EQ(response_body_type_, UNKNOWN);
  DCHECK_NE(type, UNKNOWN);
  response_body_type_ = type;
}

bool ServiceWorkerURLRequestJob::ShouldRecordResult() {
  return !did_record_result_ && is_started_ &&
         response_type_ == FORWARD_TO_SERVICE_WORKER;
}

void ServiceWorkerURLRequestJob::RecordResult(
    ServiceWorkerMetrics::URLRequestJobResult result) {
  // It violates style guidelines to handle a NOTREACHED() failure but if there
  // is a bug don't let it corrupt UMA results by double-counting.
  if (!ShouldRecordResult()) {
    NOTREACHED();
    return;
  }
  did_record_result_ = true;
  ServiceWorkerMetrics::RecordURLRequestJobResult(is_main_resource_load_,
                                                  result);
  if (request())
    request()->net_log().AddEvent(RequestJobResultToNetEventType(result));
}

void ServiceWorkerURLRequestJob::RecordStatusZeroResponseError(
    blink::WebServiceWorkerResponseError error) {
  // It violates style guidelines to handle a NOTREACHED() failure but if there
  // is a bug don't let it corrupt UMA results by double-counting.
  if (!ShouldRecordResult()) {
    NOTREACHED();
    return;
  }
  RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_RESPONSE_STATUS_ZERO);
  ServiceWorkerMetrics::RecordStatusZeroResponseError(is_main_resource_load_,
                                                      error);
}

void ServiceWorkerURLRequestJob::ClearStream() {
  if (streaming_version_) {
    streaming_version_->RemoveStreamingURLRequestJob(this);
    streaming_version_ = nullptr;
  }
  if (stream_) {
    stream_->RemoveReadObserver(this);
    stream_->Abort();
    stream_ = nullptr;
  }
  if (!waiting_stream_url_.is_empty()) {
    StreamRegistry* stream_registry =
        GetStreamContextForResourceContext(resource_context_)->registry();
    stream_registry->RemoveRegisterObserver(waiting_stream_url_);
    stream_registry->AbortPendingStream(waiting_stream_url_);
  }
}

void ServiceWorkerURLRequestJob::NotifyHeadersComplete() {
  OnStartCompleted();
  URLRequestJob::NotifyHeadersComplete();
}

void ServiceWorkerURLRequestJob::NotifyStartError(
    net::URLRequestStatus status) {
  OnStartCompleted();
  URLRequestJob::NotifyStartError(status);
}

void ServiceWorkerURLRequestJob::NotifyRestartRequired() {
  delegate_->OnPrepareToRestart(worker_start_time_, worker_ready_time_);
  URLRequestJob::NotifyRestartRequired();
}

void ServiceWorkerURLRequestJob::OnStartCompleted() const {
  if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
    delegate_->OnStartCompleted(
        false /* was_fetched_via_service_worker */,
        false /* was_fallback_required */,
        GURL() /* original_url_via_service_worker */,
        blink::WebServiceWorkerResponseTypeDefault,
        base::TimeTicks() /* service_worker_start_time */,
        base::TimeTicks() /* service_worker_ready_time */);
    return;
  }
  delegate_->OnStartCompleted(true /* was_fetched_via_service_worker */,
                              fall_back_required_, response_url_,
                              service_worker_response_type_, worker_start_time_,
                              worker_ready_time_);
}

}  // namespace content
