// 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/child/web_url_loader_impl.h"

#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/mime_util/mime_util.h"
#include "content/child/child_thread_impl.h"
#include "content/child/ftp_directory_listing_response_delegate.h"
#include "content/child/multipart_response_delegate.h"
#include "content/child/request_extra_data.h"
#include "content/child/request_info.h"
#include "content/child/resource_dispatcher.h"
#include "content/child/shared_memory_data_consumer_handle.h"
#include "content/child/sync_load_response.h"
#include "content/child/web_url_request_util.h"
#include "content/child/weburlresponse_extradata_impl.h"
#include "content/common/resource_messages.h"
#include "content/common/resource_request_body.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/child/fixed_received_data.h"
#include "content/public/child/request_peer.h"
#include "content/public/common/content_switches.h"
#include "net/base/data_url.h"
#include "net/base/filename_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_data_job.h"
#include "third_party/WebKit/public/platform/WebHTTPLoadInfo.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebURLError.h"
#include "third_party/WebKit/public/platform/WebURLLoadTiming.h"
#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/platform/WebURLResponse.h"
#include "third_party/WebKit/public/web/WebSecurityPolicy.h"

using base::Time;
using base::TimeTicks;
using blink::WebData;
using blink::WebHTTPBody;
using blink::WebHTTPHeaderVisitor;
using blink::WebHTTPLoadInfo;
using blink::WebReferrerPolicy;
using blink::WebSecurityPolicy;
using blink::WebString;
using blink::WebURL;
using blink::WebURLError;
using blink::WebURLLoadTiming;
using blink::WebURLLoader;
using blink::WebURLLoaderClient;
using blink::WebURLRequest;
using blink::WebURLResponse;

namespace content {

// Utilities ------------------------------------------------------------------

namespace {

using HeadersVector = ResourceDevToolsInfo::HeadersVector;

// Converts timing data from |load_timing| to the format used by WebKit.
void PopulateURLLoadTiming(const net::LoadTimingInfo& load_timing,
                           WebURLLoadTiming* url_timing) {
  DCHECK(!load_timing.request_start.is_null());

  const TimeTicks kNullTicks;
  url_timing->initialize();
  url_timing->setRequestTime(
      (load_timing.request_start - kNullTicks).InSecondsF());
  url_timing->setProxyStart(
      (load_timing.proxy_resolve_start - kNullTicks).InSecondsF());
  url_timing->setProxyEnd(
      (load_timing.proxy_resolve_end - kNullTicks).InSecondsF());
  url_timing->setDNSStart(
      (load_timing.connect_timing.dns_start - kNullTicks).InSecondsF());
  url_timing->setDNSEnd(
      (load_timing.connect_timing.dns_end - kNullTicks).InSecondsF());
  url_timing->setConnectStart(
      (load_timing.connect_timing.connect_start - kNullTicks).InSecondsF());
  url_timing->setConnectEnd(
      (load_timing.connect_timing.connect_end - kNullTicks).InSecondsF());
  url_timing->setSSLStart(
      (load_timing.connect_timing.ssl_start - kNullTicks).InSecondsF());
  url_timing->setSSLEnd(
      (load_timing.connect_timing.ssl_end - kNullTicks).InSecondsF());
  url_timing->setSendStart(
      (load_timing.send_start - kNullTicks).InSecondsF());
  url_timing->setSendEnd(
      (load_timing.send_end - kNullTicks).InSecondsF());
  url_timing->setReceiveHeadersEnd(
      (load_timing.receive_headers_end - kNullTicks).InSecondsF());
}

net::RequestPriority ConvertWebKitPriorityToNetPriority(
    const WebURLRequest::Priority& priority) {
  switch (priority) {
    case WebURLRequest::PriorityVeryHigh:
      return net::HIGHEST;

    case WebURLRequest::PriorityHigh:
      return net::MEDIUM;

    case WebURLRequest::PriorityMedium:
      return net::LOW;

    case WebURLRequest::PriorityLow:
      return net::LOWEST;

    case WebURLRequest::PriorityVeryLow:
      return net::IDLE;

    case WebURLRequest::PriorityUnresolved:
    default:
      NOTREACHED();
      return net::LOW;
  }
}

// Extracts info from a data scheme URL |url| into |info| and |data|. Returns
// net::OK if successful. Returns a net error code otherwise.
int GetInfoFromDataURL(const GURL& url,
                       ResourceResponseInfo* info,
                       std::string* data) {
  // Assure same time for all time fields of data: URLs.
  Time now = Time::Now();
  info->load_timing.request_start = TimeTicks::Now();
  info->load_timing.request_start_time = now;
  info->request_time = now;
  info->response_time = now;

  std::string mime_type;
  std::string charset;
  scoped_refptr<net::HttpResponseHeaders> headers(
      new net::HttpResponseHeaders(std::string()));
  int result = net::URLRequestDataJob::BuildResponse(
      url, &mime_type, &charset, data, headers.get());
  if (result != net::OK)
    return result;

  info->headers = headers;
  info->mime_type.swap(mime_type);
  info->charset.swap(charset);
  info->security_info.clear();
  info->content_length = data->length();
  info->encoded_data_length = 0;

  return net::OK;
}

#define STATIC_ASSERT_MATCHING_ENUMS(content_name, blink_name)       \
  static_assert(                                                     \
      static_cast<int>(content_name) == static_cast<int>(blink_name), \
      "mismatching enums: " #content_name)

STATIC_ASSERT_MATCHING_ENUMS(FETCH_REQUEST_MODE_SAME_ORIGIN,
                             WebURLRequest::FetchRequestModeSameOrigin);
STATIC_ASSERT_MATCHING_ENUMS(FETCH_REQUEST_MODE_NO_CORS,
                             WebURLRequest::FetchRequestModeNoCORS);
STATIC_ASSERT_MATCHING_ENUMS(FETCH_REQUEST_MODE_CORS,
                             WebURLRequest::FetchRequestModeCORS);
STATIC_ASSERT_MATCHING_ENUMS(
    FETCH_REQUEST_MODE_CORS_WITH_FORCED_PREFLIGHT,
    WebURLRequest::FetchRequestModeCORSWithForcedPreflight);

FetchRequestMode GetFetchRequestMode(const WebURLRequest& request) {
  return static_cast<FetchRequestMode>(request.fetchRequestMode());
}

STATIC_ASSERT_MATCHING_ENUMS(FETCH_CREDENTIALS_MODE_OMIT,
                             WebURLRequest::FetchCredentialsModeOmit);
STATIC_ASSERT_MATCHING_ENUMS(FETCH_CREDENTIALS_MODE_SAME_ORIGIN,
                             WebURLRequest::FetchCredentialsModeSameOrigin);
STATIC_ASSERT_MATCHING_ENUMS(FETCH_CREDENTIALS_MODE_INCLUDE,
                             WebURLRequest::FetchCredentialsModeInclude);

FetchCredentialsMode GetFetchCredentialsMode(const WebURLRequest& request) {
  return static_cast<FetchCredentialsMode>(request.fetchCredentialsMode());
}

STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_FRAME_TYPE_AUXILIARY,
                             WebURLRequest::FrameTypeAuxiliary);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_FRAME_TYPE_NESTED,
                             WebURLRequest::FrameTypeNested);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_FRAME_TYPE_NONE,
                             WebURLRequest::FrameTypeNone);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL,
                             WebURLRequest::FrameTypeTopLevel);

RequestContextFrameType GetRequestContextFrameType(
    const WebURLRequest& request) {
  return static_cast<RequestContextFrameType>(request.frameType());
}

STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_UNSPECIFIED,
                             WebURLRequest::RequestContextUnspecified);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_AUDIO,
                             WebURLRequest::RequestContextAudio);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_BEACON,
                             WebURLRequest::RequestContextBeacon);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_CSP_REPORT,
                             WebURLRequest::RequestContextCSPReport);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_DOWNLOAD,
                             WebURLRequest::RequestContextDownload);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_EMBED,
                             WebURLRequest::RequestContextEmbed);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_EVENT_SOURCE,
                             WebURLRequest::RequestContextEventSource);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_FAVICON,
                             WebURLRequest::RequestContextFavicon);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_FETCH,
                             WebURLRequest::RequestContextFetch);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_FONT,
                             WebURLRequest::RequestContextFont);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_FORM,
                             WebURLRequest::RequestContextForm);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_FRAME,
                             WebURLRequest::RequestContextFrame);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_HYPERLINK,
                             WebURLRequest::RequestContextHyperlink);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_IFRAME,
                             WebURLRequest::RequestContextIframe);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_IMAGE,
                             WebURLRequest::RequestContextImage);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_IMAGE_SET,
                             WebURLRequest::RequestContextImageSet);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_IMPORT,
                             WebURLRequest::RequestContextImport);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_INTERNAL,
                             WebURLRequest::RequestContextInternal);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_LOCATION,
                             WebURLRequest::RequestContextLocation);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_MANIFEST,
                             WebURLRequest::RequestContextManifest);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_OBJECT,
                             WebURLRequest::RequestContextObject);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_PING,
                             WebURLRequest::RequestContextPing);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_PLUGIN,
                             WebURLRequest::RequestContextPlugin);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_PREFETCH,
                             WebURLRequest::RequestContextPrefetch);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_SCRIPT,
                             WebURLRequest::RequestContextScript);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_SERVICE_WORKER,
                             WebURLRequest::RequestContextServiceWorker);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_SHARED_WORKER,
                             WebURLRequest::RequestContextSharedWorker);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_SUBRESOURCE,
                             WebURLRequest::RequestContextSubresource);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_STYLE,
                             WebURLRequest::RequestContextStyle);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_TRACK,
                             WebURLRequest::RequestContextTrack);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_VIDEO,
                             WebURLRequest::RequestContextVideo);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_WORKER,
                             WebURLRequest::RequestContextWorker);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_XML_HTTP_REQUEST,
                             WebURLRequest::RequestContextXMLHttpRequest);
STATIC_ASSERT_MATCHING_ENUMS(REQUEST_CONTEXT_TYPE_XSLT,
                             WebURLRequest::RequestContextXSLT);

RequestContextType GetRequestContextType(const WebURLRequest& request) {
  return static_cast<RequestContextType>(request.requestContext());
}

}  // namespace

// WebURLLoaderImpl::Context --------------------------------------------------

// This inner class exists since the WebURLLoader may be deleted while inside a
// call to WebURLLoaderClient.  Refcounting is to keep the context from being
// deleted if it may have work to do after calling into the client.
class WebURLLoaderImpl::Context : public base::RefCounted<Context>,
                                  public RequestPeer {
 public:
  Context(WebURLLoaderImpl* loader,
          ResourceDispatcher* resource_dispatcher,
          scoped_refptr<base::SingleThreadTaskRunner> task_runner);

  WebURLLoaderClient* client() const { return client_; }
  void set_client(WebURLLoaderClient* client) { client_ = client; }

  void Cancel();
  void SetDefersLoading(bool value);
  void DidChangePriority(WebURLRequest::Priority new_priority,
                         int intra_priority_value);
  bool AttachThreadedDataReceiver(
      blink::WebThreadedDataReceiver* threaded_data_receiver);
  void Start(const WebURLRequest& request,
             SyncLoadResponse* sync_load_response);

  // RequestPeer methods:
  void OnUploadProgress(uint64 position, uint64 size) override;
  bool OnReceivedRedirect(const net::RedirectInfo& redirect_info,
                          const ResourceResponseInfo& info) override;
  void OnReceivedResponse(const ResourceResponseInfo& info) override;
  void OnDownloadedData(int len, int encoded_data_length) override;
  void OnReceivedData(scoped_ptr<ReceivedData> data) override;
  void OnReceivedCachedMetadata(const char* data, int len) override;
  void OnCompletedRequest(int error_code,
                          bool was_ignored_by_handler,
                          bool stale_copy_in_cache,
                          const std::string& security_info,
                          const base::TimeTicks& completion_time,
                          int64 total_transfer_size) override;
  void OnReceivedCompletedResponse(const ResourceResponseInfo& info,
                                   scoped_ptr<ReceivedData> data,
                                   int error_code,
                                   bool was_ignored_by_handler,
                                   bool stale_copy_in_cache,
                                   const std::string& security_info,
                                   const base::TimeTicks& completion_time,
                                   int64 total_transfer_size) override;

 private:
  friend class base::RefCounted<Context>;
  ~Context() override;

  // Called when the body data stream is detached from the reader side.
  void CancelBodyStreaming();
  // We can optimize the handling of data URLs in most cases.
  bool CanHandleDataURLRequestLocally() const;
  void HandleDataURL();

  WebURLLoaderImpl* loader_;
  WebURLRequest request_;
  WebURLLoaderClient* client_;
  ResourceDispatcher* resource_dispatcher_;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
  WebReferrerPolicy referrer_policy_;
  scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_;
  scoped_ptr<MultipartResponseDelegate> multipart_delegate_;
  scoped_ptr<StreamOverrideParameters> stream_override_;
  scoped_ptr<SharedMemoryDataConsumerHandle::Writer> body_stream_writer_;
  enum DeferState {NOT_DEFERRING, SHOULD_DEFER, DEFERRED_DATA};
  DeferState defers_loading_;
  int request_id_;
};

WebURLLoaderImpl::Context::Context(
    WebURLLoaderImpl* loader,
    ResourceDispatcher* resource_dispatcher,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    : loader_(loader),
      client_(NULL),
      resource_dispatcher_(resource_dispatcher),
      task_runner_(task_runner),
      referrer_policy_(blink::WebReferrerPolicyDefault),
      defers_loading_(NOT_DEFERRING),
      request_id_(-1) {
}

void WebURLLoaderImpl::Context::Cancel() {
  if (resource_dispatcher_ && // NULL in unittest.
      request_id_ != -1) {
    resource_dispatcher_->Cancel(request_id_);
    request_id_ = -1;
  }

  if (body_stream_writer_)
    body_stream_writer_->Fail();

  // Ensure that we do not notify the multipart delegate anymore as it has
  // its own pointer to the client.
  if (multipart_delegate_)
    multipart_delegate_->Cancel();
  // Ditto for the ftp delegate.
  if (ftp_listing_delegate_)
    ftp_listing_delegate_->Cancel();

  // Do not make any further calls to the client.
  client_ = NULL;
  loader_ = NULL;
}

void WebURLLoaderImpl::Context::SetDefersLoading(bool value) {
  if (request_id_ != -1)
    resource_dispatcher_->SetDefersLoading(request_id_, value);
  if (value && defers_loading_ == NOT_DEFERRING) {
    defers_loading_ = SHOULD_DEFER;
  } else if (!value && defers_loading_ != NOT_DEFERRING) {
    if (defers_loading_ == DEFERRED_DATA) {
      task_runner_->PostTask(FROM_HERE,
                             base::Bind(&Context::HandleDataURL, this));
    }
    defers_loading_ = NOT_DEFERRING;
  }
}

void WebURLLoaderImpl::Context::DidChangePriority(
    WebURLRequest::Priority new_priority, int intra_priority_value) {
  if (request_id_ != -1) {
    resource_dispatcher_->DidChangePriority(
        request_id_,
        ConvertWebKitPriorityToNetPriority(new_priority),
        intra_priority_value);
  }
}

bool WebURLLoaderImpl::Context::AttachThreadedDataReceiver(
    blink::WebThreadedDataReceiver* threaded_data_receiver) {
  if (request_id_ != -1) {
    return resource_dispatcher_->AttachThreadedDataReceiver(
        request_id_, threaded_data_receiver);
  }

  return false;
}

void WebURLLoaderImpl::Context::Start(const WebURLRequest& request,
                                      SyncLoadResponse* sync_load_response) {
  DCHECK(request_id_ == -1);
  request_ = request;  // Save the request.
  if (request.extraData()) {
    RequestExtraData* extra_data =
        static_cast<RequestExtraData*>(request.extraData());
    stream_override_ = extra_data->TakeStreamOverrideOwnership();
  }

  GURL url = request.url();

  // PlzNavigate: during navigation, the renderer should request a stream which
  // contains the body of the response. The request has already been made by the
  // browser.
  if (stream_override_.get()) {
    CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableBrowserSideNavigation));
    DCHECK(!sync_load_response);
    DCHECK_NE(WebURLRequest::FrameTypeNone, request.frameType());
    DCHECK_EQ("GET", request.httpMethod().latin1());
    url = stream_override_->stream_url;
  }

  if (CanHandleDataURLRequestLocally()) {
    if (sync_load_response) {
      // This is a sync load. Do the work now.
      sync_load_response->url = url;
      sync_load_response->error_code =
          GetInfoFromDataURL(sync_load_response->url, sync_load_response,
                             &sync_load_response->data);
    } else {
      task_runner_->PostTask(FROM_HERE,
                             base::Bind(&Context::HandleDataURL, this));
    }
    return;
  }

  // PlzNavigate: outside of tests, the only navigation requests going through
  // the WebURLLoader are the ones created by CommitNavigation. Several browser
  // tests load HTML directly through a data url which will be handled by the
  // block above.
  DCHECK_IMPLIES(base::CommandLine::ForCurrentProcess()->HasSwitch(
                     switches::kEnableBrowserSideNavigation),
                 stream_override_.get() ||
                     request.frameType() == WebURLRequest::FrameTypeNone);

  GURL referrer_url(
      request.httpHeaderField(WebString::fromUTF8("Referer")).latin1());
  const std::string& method = request.httpMethod().latin1();

  // TODO(brettw) this should take parameter encoding into account when
  // creating the GURLs.

  // TODO(horo): Check credentials flag is unset when credentials mode is omit.
  //             Check credentials flag is set when credentials mode is include.

  RequestInfo request_info;
  request_info.method = method;
  request_info.url = url;
  request_info.first_party_for_cookies = request.firstPartyForCookies();
  referrer_policy_ = request.referrerPolicy();
  request_info.referrer = Referrer(referrer_url, referrer_policy_);
  request_info.headers = GetWebURLRequestHeaders(request);
  request_info.load_flags = GetLoadFlagsForWebURLRequest(request);
  request_info.enable_load_timing = true;
  request_info.enable_upload_progress = request.reportUploadProgress();
  if (request.requestContext() == WebURLRequest::RequestContextXMLHttpRequest &&
      (url.has_username() || url.has_password())) {
    request_info.do_not_prompt_for_login = true;
  }
  // requestor_pid only needs to be non-zero if the request originates outside
  // the render process, so we can use requestorProcessID even for requests
  // from in-process plugins.
  request_info.requestor_pid = request.requestorProcessID();
  request_info.request_type = WebURLRequestToResourceType(request);
  request_info.priority =
      ConvertWebKitPriorityToNetPriority(request.priority());
  request_info.appcache_host_id = request.appCacheHostID();
  request_info.routing_id = request.requestorID();
  request_info.download_to_file = request.downloadToFile();
  request_info.has_user_gesture = request.hasUserGesture();
  request_info.skip_service_worker = request.skipServiceWorker();
  request_info.should_reset_appcache = request.shouldResetAppCache();
  request_info.fetch_request_mode = GetFetchRequestMode(request);
  request_info.fetch_credentials_mode = GetFetchCredentialsMode(request);
  request_info.fetch_request_context_type = GetRequestContextType(request);
  request_info.fetch_frame_type = GetRequestContextFrameType(request);
  request_info.extra_data = request.extraData();

  scoped_refptr<ResourceRequestBody> request_body =
      GetRequestBodyForWebURLRequest(request).get();

  if (sync_load_response) {
    resource_dispatcher_->StartSync(
        request_info, request_body.get(), sync_load_response);
    return;
  }

  request_id_ = resource_dispatcher_->StartAsync(
      request_info, request_body.get(), this);
}

void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) {
  if (client_)
    client_->didSendData(loader_, position, size);
}

bool WebURLLoaderImpl::Context::OnReceivedRedirect(
    const net::RedirectInfo& redirect_info,
    const ResourceResponseInfo& info) {
  if (!client_)
    return false;

  WebURLResponse response;
  response.initialize();
  PopulateURLResponse(request_.url(), info, &response);

  // TODO(darin): We lack sufficient information to construct the actual
  // request that resulted from the redirect.
  WebURLRequest new_request(redirect_info.new_url);
  new_request.setFirstPartyForCookies(
      redirect_info.new_first_party_for_cookies);
  new_request.setDownloadToFile(request_.downloadToFile());
  new_request.setUseStreamOnResponse(request_.useStreamOnResponse());
  new_request.setRequestContext(request_.requestContext());
  new_request.setFrameType(request_.frameType());
  new_request.setSkipServiceWorker(!info.was_fetched_via_service_worker);
  new_request.setShouldResetAppCache(request_.shouldResetAppCache());
  new_request.setFetchRequestMode(request_.fetchRequestMode());
  new_request.setFetchCredentialsMode(request_.fetchCredentialsMode());

  new_request.setHTTPReferrer(WebString::fromUTF8(redirect_info.new_referrer),
                              referrer_policy_);

  std::string old_method = request_.httpMethod().utf8();
  new_request.setHTTPMethod(WebString::fromUTF8(redirect_info.new_method));
  if (redirect_info.new_method == old_method)
    new_request.setHTTPBody(request_.httpBody());

  // Protect from deletion during call to willSendRequest.
  scoped_refptr<Context> protect(this);

  client_->willSendRequest(loader_, new_request, response);
  request_ = new_request;

  // Only follow the redirect if WebKit left the URL unmodified.
  if (redirect_info.new_url == GURL(new_request.url())) {
    // First-party cookie logic moved from DocumentLoader in Blink to
    // net::URLRequest in the browser. Assert that Blink didn't try to change it
    // to something else.
    DCHECK_EQ(redirect_info.new_first_party_for_cookies.spec(),
              request_.firstPartyForCookies().string().utf8());
    return true;
  }

  // We assume that WebKit only changes the URL to suppress a redirect, and we
  // assume that it does so by setting it to be invalid.
  DCHECK(!new_request.url().isValid());
  return false;
}

void WebURLLoaderImpl::Context::OnReceivedResponse(
    const ResourceResponseInfo& initial_info) {
  if (!client_)
    return;

  ResourceResponseInfo info = initial_info;

  // PlzNavigate: during navigations, the ResourceResponse has already been
  // received on the browser side, and has been passed down to the renderer.
  if (stream_override_.get()) {
    CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableBrowserSideNavigation));
    info = stream_override_->response;
  }

  WebURLResponse response;
  response.initialize();
  PopulateURLResponse(request_.url(), info, &response);

  bool show_raw_listing = (GURL(request_.url()).query() == "raw");

  if (info.mime_type == "text/vnd.chromium.ftp-dir") {
    if (show_raw_listing) {
      // Set the MIME type to plain text to prevent any active content.
      response.setMIMEType("text/plain");
    } else {
      // We're going to produce a parsed listing in HTML.
      response.setMIMEType("text/html");
    }
  }

  // Prevent |this| from being destroyed if the client destroys the loader,
  // ether in didReceiveResponse, or when the multipart/ftp delegate calls into
  // it.
  scoped_refptr<Context> protect(this);

  if (request_.useStreamOnResponse()) {
    SharedMemoryDataConsumerHandle::BackpressureMode mode =
        SharedMemoryDataConsumerHandle::kDoNotApplyBackpressure;
    if (info.headers &&
        info.headers->HasHeaderValue("Cache-Control", "no-store")) {
      mode = SharedMemoryDataConsumerHandle::kApplyBackpressure;
    }

    auto read_handle = make_scoped_ptr(new SharedMemoryDataConsumerHandle(
        mode, base::Bind(&Context::CancelBodyStreaming, this),
        &body_stream_writer_));

    // Here |body_stream_writer_| has an indirect reference to |this| and that
    // creates a reference cycle, but it is not a problem because the cycle
    // will break if one of the following happens:
    //  1) The body data transfer is done (with or without an error).
    //  2) |read_handle| (and its reader) is detached.

    // The client takes |read_handle|'s ownership.
    client_->didReceiveResponse(loader_, response, read_handle.release());
    // TODO(yhirano): Support ftp listening and multipart
    return;
  } else {
    client_->didReceiveResponse(loader_, response);
  }

  // We may have been cancelled after didReceiveResponse, which would leave us
  // without a client and therefore without much need to do further handling.
  if (!client_)
    return;

  DCHECK(!ftp_listing_delegate_.get());
  DCHECK(!multipart_delegate_.get());
  if (info.headers.get() && info.mime_type == "multipart/x-mixed-replace") {
    std::string content_type;
    info.headers->EnumerateHeader(NULL, "content-type", &content_type);

    std::string mime_type;
    std::string charset;
    bool had_charset = false;
    std::string boundary;
    net::HttpUtil::ParseContentType(content_type, &mime_type, &charset,
                                    &had_charset, &boundary);
    base::TrimString(boundary, " \"", &boundary);

    // If there's no boundary, just handle the request normally.  In the gecko
    // code, nsMultiMixedConv::OnStartRequest throws an exception.
    if (!boundary.empty()) {
      multipart_delegate_.reset(
          new MultipartResponseDelegate(client_, loader_, response, boundary));
    }
  } else if (info.mime_type == "text/vnd.chromium.ftp-dir" &&
             !show_raw_listing) {
    ftp_listing_delegate_.reset(
        new FtpDirectoryListingResponseDelegate(client_, loader_, response));
  }
}

void WebURLLoaderImpl::Context::OnDownloadedData(int len,
                                                 int encoded_data_length) {
  if (client_)
    client_->didDownloadData(loader_, len, encoded_data_length);
}

void WebURLLoaderImpl::Context::OnReceivedData(scoped_ptr<ReceivedData> data) {
  const char* payload = data->payload();
  int data_length = data->length();
  int encoded_data_length = data->encoded_length();
  if (!client_)
    return;

  if (ftp_listing_delegate_) {
    // The FTP listing delegate will make the appropriate calls to
    // client_->didReceiveData and client_->didReceiveResponse.  Since the
    // delegate may want to do work after sending data to the delegate, keep
    // |this| and the delegate alive until it's finished handling the data.
    scoped_refptr<Context> protect(this);
    ftp_listing_delegate_->OnReceivedData(payload, data_length);
  } else if (multipart_delegate_) {
    // The multipart delegate will make the appropriate calls to
    // client_->didReceiveData and client_->didReceiveResponse.  Since the
    // delegate may want to do work after sending data to the delegate, keep
    // |this| and the delegate alive until it's finished handling the data.
    scoped_refptr<Context> protect(this);
    multipart_delegate_->OnReceivedData(payload, data_length,
                                        encoded_data_length);
  } else {
    scoped_refptr<Context> protect(this);
    // We dispatch the data even when |useStreamOnResponse()| is set, in order
    // to make Devtools work.
    client_->didReceiveData(loader_, payload, data_length, encoded_data_length);

    if (request_.useStreamOnResponse()) {
      // We don't support ftp_listening_delegate_ and multipart_delegate_ for
      // now.
      // TODO(yhirano): Support ftp listening and multipart.
      body_stream_writer_->AddData(data.Pass());
    }
  }
}

void WebURLLoaderImpl::Context::OnReceivedCachedMetadata(
    const char* data, int len) {
  if (client_)
    client_->didReceiveCachedMetadata(loader_, data, len);
}

void WebURLLoaderImpl::Context::OnCompletedRequest(
    int error_code,
    bool was_ignored_by_handler,
    bool stale_copy_in_cache,
    const std::string& security_info,
    const base::TimeTicks& completion_time,
    int64 total_transfer_size) {
  // The WebURLLoaderImpl may be deleted in any of the calls to the client or
  // the delegates below (As they also may call in to the client).  Keep |this|
  // alive in that case, to avoid a crash.  If that happens, the request will be
  // cancelled and |client_| will be set to NULL.
  scoped_refptr<Context> protect(this);

  if (ftp_listing_delegate_) {
    ftp_listing_delegate_->OnCompletedRequest();
    ftp_listing_delegate_.reset(NULL);
  } else if (multipart_delegate_) {
    multipart_delegate_->OnCompletedRequest();
    multipart_delegate_.reset(NULL);
  }

  if (body_stream_writer_ && error_code != net::OK)
    body_stream_writer_->Fail();
  body_stream_writer_.reset();

  if (client_) {
    if (error_code != net::OK) {
      client_->didFail(
          loader_,
          CreateWebURLError(request_.url(), stale_copy_in_cache, error_code,
              was_ignored_by_handler));
    } else {
      client_->didFinishLoading(loader_,
                                (completion_time - TimeTicks()).InSecondsF(),
                                total_transfer_size);
    }
  }
}

void WebURLLoaderImpl::Context::OnReceivedCompletedResponse(
    const ResourceResponseInfo& info,
    scoped_ptr<ReceivedData> data,
    int error_code,
    bool was_ignored_by_handler,
    bool stale_copy_in_cache,
    const std::string& security_info,
    const base::TimeTicks& completion_time,
    int64 total_transfer_size) {
  scoped_refptr<Context> protect(this);

  OnReceivedResponse(info);
  if (data)
    OnReceivedData(data.Pass());
  OnCompletedRequest(error_code, was_ignored_by_handler, stale_copy_in_cache,
                     security_info, completion_time, total_transfer_size);
}

WebURLLoaderImpl::Context::~Context() {
  if (request_id_ >= 0) {
    resource_dispatcher_->RemovePendingRequest(request_id_);
  }
}

void WebURLLoaderImpl::Context::CancelBodyStreaming() {
  scoped_refptr<Context> protect(this);

  // Notify renderer clients that the request is canceled.
  if (ftp_listing_delegate_) {
    ftp_listing_delegate_->OnCompletedRequest();
    ftp_listing_delegate_.reset(NULL);
  } else if (multipart_delegate_) {
    multipart_delegate_->OnCompletedRequest();
    multipart_delegate_.reset(NULL);
  }

  if (body_stream_writer_) {
    body_stream_writer_->Fail();
    body_stream_writer_.reset();
  }
  if (client_) {
    // TODO(yhirano): Set |stale_copy_in_cache| appropriately if possible.
    client_->didFail(
        loader_, CreateWebURLError(request_.url(), false, net::ERR_ABORTED));
  }

  // Notify the browser process that the request is canceled.
  Cancel();
}

bool WebURLLoaderImpl::Context::CanHandleDataURLRequestLocally() const {
  GURL url = request_.url();
  if (!url.SchemeIs(url::kDataScheme))
    return false;

  // The fast paths for data URL, Start() and HandleDataURL(), don't support
  // the downloadToFile option.
  if (request_.downloadToFile())
    return false;

  // Data url requests from object tags may need to be intercepted as streams
  // and so need to be sent to the browser.
  if (request_.requestContext() == WebURLRequest::RequestContextObject)
    return false;

  // Optimize for the case where we can handle a data URL locally.  We must
  // skip this for data URLs targetted at frames since those could trigger a
  // download.
  //
  // NOTE: We special case MIME types we can render both for performance
  // reasons as well as to support unit tests.

#if defined(OS_ANDROID)
  // For compatibility reasons on Android we need to expose top-level data://
  // to the browser.
  if (request_.frameType() == WebURLRequest::FrameTypeTopLevel)
    return false;
#endif

  if (request_.frameType() != WebURLRequest::FrameTypeTopLevel &&
      request_.frameType() != WebURLRequest::FrameTypeNested)
    return true;

  std::string mime_type, unused_charset;
  if (net::DataURL::Parse(request_.url(), &mime_type, &unused_charset, NULL) &&
      mime_util::IsSupportedMimeType(mime_type))
    return true;

  return false;
}

void WebURLLoaderImpl::Context::HandleDataURL() {
  DCHECK_NE(defers_loading_, DEFERRED_DATA);
  if (defers_loading_ == SHOULD_DEFER) {
      defers_loading_ = DEFERRED_DATA;
      return;
  }

  ResourceResponseInfo info;
  std::string data;

  int error_code = GetInfoFromDataURL(request_.url(), &info, &data);

  if (error_code == net::OK) {
    OnReceivedResponse(info);
    if (!data.empty())
      OnReceivedData(
          make_scoped_ptr(new FixedReceivedData(data.data(), data.size(), 0)));
  }

  OnCompletedRequest(error_code, false, false, info.security_info,
                     base::TimeTicks::Now(), 0);
}

// WebURLLoaderImpl -----------------------------------------------------------

WebURLLoaderImpl::WebURLLoaderImpl(
    ResourceDispatcher* resource_dispatcher,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    : context_(new Context(this, resource_dispatcher, task_runner)) {
}

WebURLLoaderImpl::~WebURLLoaderImpl() {
  cancel();
}

void WebURLLoaderImpl::PopulateURLResponse(const GURL& url,
                                           const ResourceResponseInfo& info,
                                           WebURLResponse* response) {
  response->setURL(url);
  response->setResponseTime(info.response_time.ToInternalValue());
  response->setMIMEType(WebString::fromUTF8(info.mime_type));
  response->setTextEncodingName(WebString::fromUTF8(info.charset));
  response->setExpectedContentLength(info.content_length);
  response->setSecurityInfo(info.security_info);
  response->setAppCacheID(info.appcache_id);
  response->setAppCacheManifestURL(info.appcache_manifest_url);
  response->setWasCached(!info.load_timing.request_start_time.is_null() &&
      info.response_time < info.load_timing.request_start_time);
  response->setRemoteIPAddress(
      WebString::fromUTF8(info.socket_address.host()));
  response->setRemotePort(info.socket_address.port());
  response->setConnectionID(info.load_timing.socket_log_id);
  response->setConnectionReused(info.load_timing.socket_reused);
  response->setDownloadFilePath(info.download_file_path.AsUTF16Unsafe());
  response->setWasFetchedViaSPDY(info.was_fetched_via_spdy);
  response->setWasFetchedViaServiceWorker(info.was_fetched_via_service_worker);
  response->setWasFallbackRequiredByServiceWorker(
      info.was_fallback_required_by_service_worker);
  response->setServiceWorkerResponseType(info.response_type_via_service_worker);
  response->setOriginalURLViaServiceWorker(
      info.original_url_via_service_worker);

  WebURLResponseExtraDataImpl* extra_data =
      new WebURLResponseExtraDataImpl(info.npn_negotiated_protocol);
  response->setExtraData(extra_data);
  extra_data->set_was_fetched_via_spdy(info.was_fetched_via_spdy);
  extra_data->set_was_npn_negotiated(info.was_npn_negotiated);
  extra_data->set_was_alternate_protocol_available(
      info.was_alternate_protocol_available);
  extra_data->set_connection_info(info.connection_info);
  extra_data->set_was_fetched_via_proxy(info.was_fetched_via_proxy);
  extra_data->set_proxy_server(info.proxy_server);

  // If there's no received headers end time, don't set load timing.  This is
  // the case for non-HTTP requests, requests that don't go over the wire, and
  // certain error cases.
  if (!info.load_timing.receive_headers_end.is_null()) {
    WebURLLoadTiming timing;
    PopulateURLLoadTiming(info.load_timing, &timing);
    const TimeTicks kNullTicks;
    timing.setWorkerStart(
        (info.service_worker_start_time - kNullTicks).InSecondsF());
    timing.setWorkerReady(
        (info.service_worker_ready_time - kNullTicks).InSecondsF());
    response->setLoadTiming(timing);
  }

  if (info.devtools_info.get()) {
    WebHTTPLoadInfo load_info;

    load_info.setHTTPStatusCode(info.devtools_info->http_status_code);
    load_info.setHTTPStatusText(WebString::fromLatin1(
        info.devtools_info->http_status_text));
    load_info.setEncodedDataLength(info.encoded_data_length);

    load_info.setRequestHeadersText(WebString::fromLatin1(
        info.devtools_info->request_headers_text));
    load_info.setResponseHeadersText(WebString::fromLatin1(
        info.devtools_info->response_headers_text));
    const HeadersVector& request_headers = info.devtools_info->request_headers;
    for (HeadersVector::const_iterator it = request_headers.begin();
         it != request_headers.end(); ++it) {
      load_info.addRequestHeader(WebString::fromLatin1(it->first),
          WebString::fromLatin1(it->second));
    }
    const HeadersVector& response_headers =
        info.devtools_info->response_headers;
    for (HeadersVector::const_iterator it = response_headers.begin();
         it != response_headers.end(); ++it) {
      load_info.addResponseHeader(WebString::fromLatin1(it->first),
          WebString::fromLatin1(it->second));
    }
    load_info.setNPNNegotiatedProtocol(WebString::fromLatin1(
        info.npn_negotiated_protocol));
    response->setHTTPLoadInfo(load_info);
  }

  const net::HttpResponseHeaders* headers = info.headers.get();
  if (!headers)
    return;

  WebURLResponse::HTTPVersion version = WebURLResponse::Unknown;
  if (headers->GetHttpVersion() == net::HttpVersion(0, 9))
    version = WebURLResponse::HTTP_0_9;
  else if (headers->GetHttpVersion() == net::HttpVersion(1, 0))
    version = WebURLResponse::HTTP_1_0;
  else if (headers->GetHttpVersion() == net::HttpVersion(1, 1))
    version = WebURLResponse::HTTP_1_1;
  response->setHTTPVersion(version);
  response->setHTTPStatusCode(headers->response_code());
  response->setHTTPStatusText(WebString::fromLatin1(headers->GetStatusText()));

  // TODO(darin): We should leverage HttpResponseHeaders for this, and this
  // should be using the same code as ResourceDispatcherHost.
  // TODO(jungshik): Figure out the actual value of the referrer charset and
  // pass it to GetSuggestedFilename.
  std::string value;
  headers->EnumerateHeader(NULL, "content-disposition", &value);
  response->setSuggestedFileName(
      net::GetSuggestedFilename(url,
                                value,
                                std::string(),  // referrer_charset
                                std::string(),  // suggested_name
                                std::string(),  // mime_type
                                std::string()));  // default_name

  Time time_val;
  if (headers->GetLastModifiedValue(&time_val))
    response->setLastModifiedDate(time_val.ToDoubleT());

  // Build up the header map.
  void* iter = NULL;
  std::string name;
  while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
    response->addHTTPHeaderField(WebString::fromLatin1(name),
                                 WebString::fromLatin1(value));
  }
}

void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request,
                                         WebURLResponse& response,
                                         WebURLError& error,
                                         WebData& data) {
  SyncLoadResponse sync_load_response;
  context_->Start(request, &sync_load_response);

  const GURL& final_url = sync_load_response.url;

  // TODO(tc): For file loads, we may want to include a more descriptive
  // status code or status text.
  int error_code = sync_load_response.error_code;
  if (error_code != net::OK) {
    response.setURL(final_url);
    error.domain = WebString::fromUTF8(net::kErrorDomain);
    error.reason = error_code;
    error.unreachableURL = final_url;
    return;
  }

  PopulateURLResponse(final_url, sync_load_response, &response);

  data.assign(sync_load_response.data.data(),
              sync_load_response.data.size());
}

void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request,
                                          WebURLLoaderClient* client) {
  DCHECK(!context_->client());

  context_->set_client(client);
  context_->Start(request, NULL);
}

void WebURLLoaderImpl::cancel() {
  context_->Cancel();
}

void WebURLLoaderImpl::setDefersLoading(bool value) {
  context_->SetDefersLoading(value);
}

void WebURLLoaderImpl::didChangePriority(WebURLRequest::Priority new_priority,
                                         int intra_priority_value) {
  context_->DidChangePriority(new_priority, intra_priority_value);
}

bool WebURLLoaderImpl::attachThreadedDataReceiver(
    blink::WebThreadedDataReceiver* threaded_data_receiver) {
  return context_->AttachThreadedDataReceiver(threaded_data_receiver);
}

}  // namespace content
