blob: 40e38e5616fbd8d7269e600073af8b1c41b91cab [file] [log] [blame]
// Copyright 2017 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/network/url_loader_impl.h"
#include <string>
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/common/loader_util.h"
#include "content/network/network_context.h"
#include "content/public/common/referrer.h"
#include "content/public/common/resource_response.h"
#include "content/public/common/url_loader_factory.mojom.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/load_flags.h"
#include "net/base/mime_sniffer.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/cpp/net_adapters.h"
namespace content {
namespace {
constexpr size_t kDefaultAllocationSize = 512 * 1024;
// TODO: this duplicates ResourceDispatcherHostImpl::BuildLoadFlagsForRequest.
int BuildLoadFlagsForRequest(const ResourceRequest& request,
bool is_sync_load) {
int load_flags = request.load_flags;
// Although EV status is irrelevant to sub-frames and sub-resources, we have
// to perform EV certificate verification on all resources because an HTTP
// keep-alive connection created to load a sub-frame or a sub-resource could
// be reused to load a main frame.
load_flags |= net::LOAD_VERIFY_EV_CERT;
if (request.resource_type == RESOURCE_TYPE_MAIN_FRAME) {
load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED;
} else if (request.resource_type == RESOURCE_TYPE_PREFETCH) {
load_flags |= net::LOAD_PREFETCH;
}
if (is_sync_load)
load_flags |= net::LOAD_IGNORE_LIMITS;
return load_flags;
}
// TODO: this duplicates some of PopulateResourceResponse in
// content/browser/loader/resource_loader.cc
void PopulateResourceResponse(net::URLRequest* request,
ResourceResponse* response) {
response->head.request_time = request->request_time();
response->head.response_time = request->response_time();
response->head.headers = request->response_headers();
request->GetCharset(&response->head.charset);
response->head.content_length = request->GetExpectedContentSize();
request->GetMimeType(&response->head.mime_type);
net::HttpResponseInfo response_info = request->response_info();
response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy;
response->head.was_alpn_negotiated = response_info.was_alpn_negotiated;
response->head.alpn_negotiated_protocol =
response_info.alpn_negotiated_protocol;
response->head.connection_info = response_info.connection_info;
response->head.socket_address = response_info.socket_address;
response->head.effective_connection_type =
net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
request->GetLoadTimingInfo(&response->head.load_timing);
response->head.request_start = request->creation_time();
response->head.response_start = base::TimeTicks::Now();
response->head.encoded_data_length = request->GetTotalReceivedBytes();
}
// A subclass of net::UploadBytesElementReader which owns
// ResourceRequestBody.
class BytesElementReader : public net::UploadBytesElementReader {
public:
BytesElementReader(ResourceRequestBody* resource_request_body,
const ResourceRequestBody::Element& element)
: net::UploadBytesElementReader(element.bytes(), element.length()),
resource_request_body_(resource_request_body) {
DCHECK_EQ(ResourceRequestBody::Element::TYPE_BYTES, element.type());
}
~BytesElementReader() override {}
private:
scoped_refptr<ResourceRequestBody> resource_request_body_;
DISALLOW_COPY_AND_ASSIGN(BytesElementReader);
};
// A subclass of net::UploadFileElementReader which owns
// ResourceRequestBody.
// This class is necessary to ensure the BlobData and any attached shareable
// files survive until upload completion.
class FileElementReader : public net::UploadFileElementReader {
public:
FileElementReader(ResourceRequestBody* resource_request_body,
base::TaskRunner* task_runner,
const ResourceRequestBody::Element& element)
: net::UploadFileElementReader(task_runner,
element.path(),
element.offset(),
element.length(),
element.expected_modification_time()),
resource_request_body_(resource_request_body) {
DCHECK_EQ(ResourceRequestBody::Element::TYPE_FILE, element.type());
}
~FileElementReader() override {}
private:
scoped_refptr<ResourceRequestBody> resource_request_body_;
DISALLOW_COPY_AND_ASSIGN(FileElementReader);
};
// TODO: copied from content/browser/loader/upload_data_stream_builder.cc.
std::unique_ptr<net::UploadDataStream> CreateUploadDataStream(
ResourceRequestBody* body,
base::SequencedTaskRunner* file_task_runner) {
std::vector<std::unique_ptr<net::UploadElementReader>> element_readers;
for (const auto& element : *body->elements()) {
switch (element.type()) {
case ResourceRequestBody::Element::TYPE_BYTES:
element_readers.push_back(
base::MakeUnique<BytesElementReader>(body, element));
break;
case ResourceRequestBody::Element::TYPE_FILE:
element_readers.push_back(base::MakeUnique<FileElementReader>(
body, file_task_runner, element));
break;
case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
NOTIMPLEMENTED();
break;
case ResourceRequestBody::Element::TYPE_BLOB: {
NOTIMPLEMENTED();
break;
}
case ResourceRequestBody::Element::TYPE_DISK_CACHE_ENTRY:
case ResourceRequestBody::Element::TYPE_BYTES_DESCRIPTION:
case ResourceRequestBody::Element::TYPE_UNKNOWN:
NOTREACHED();
break;
}
}
return base::MakeUnique<net::ElementsUploadDataStream>(
std::move(element_readers), body->identifier());
}
} // namespace
URLLoaderImpl::URLLoaderImpl(
NetworkContext* context,
mojom::URLLoaderRequest url_loader_request,
int32_t options,
const ResourceRequest& request,
mojom::URLLoaderClientPtr url_loader_client,
const net::NetworkTrafficAnnotationTag& traffic_annotation)
: context_(context),
options_(options),
connected_(true),
binding_(this, std::move(url_loader_request)),
url_loader_client_(std::move(url_loader_client)),
writable_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL),
peer_closed_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL),
report_raw_headers_(false),
weak_ptr_factory_(this) {
// TODO(caseq): Make sure the client renderer actually has premissions to
// get raw headers (i.e. has DevTools attached).
report_raw_headers_ = request.report_raw_headers;
context_->RegisterURLLoader(this);
binding_.set_connection_error_handler(base::BindOnce(
&URLLoaderImpl::OnConnectionError, base::Unretained(this)));
url_request_ = context_->url_request_context()->CreateRequest(
GURL(request.url), net::DEFAULT_PRIORITY, this, traffic_annotation);
url_request_->set_method(request.method);
url_request_->set_site_for_cookies(request.site_for_cookies);
const Referrer referrer(request.referrer, request.referrer_policy);
Referrer::SetReferrerForRequest(url_request_.get(), referrer);
net::HttpRequestHeaders headers;
headers.AddHeadersFromString(request.headers);
url_request_->SetExtraRequestHeaders(headers);
// Resolve elements from request_body and prepare upload data.
if (request.request_body.get()) {
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE});
url_request_->set_upload(
CreateUploadDataStream(request.request_body.get(), task_runner.get()));
}
int load_flags = BuildLoadFlagsForRequest(request, false);
url_request_->SetLoadFlags(load_flags);
if (report_raw_headers_) {
url_request_->SetRequestHeadersCallback(
base::Bind(&net::HttpRawRequestHeaders::Assign,
base::Unretained(&raw_request_headers_)));
url_request_->SetResponseHeadersCallback(base::Bind(
&URLLoaderImpl::SetRawResponseHeaders, base::Unretained(this)));
}
url_request_->Start();
}
URLLoaderImpl::~URLLoaderImpl() {
context_->DeregisterURLLoader(this);
}
void URLLoaderImpl::Cleanup() {
// The associated network context is going away and we have to destroy
// net::URLRequest held by this loader.
delete this;
}
void URLLoaderImpl::FollowRedirect() {
if (!url_request_) {
NotifyCompleted(net::ERR_UNEXPECTED);
// |this| may have been deleted.
return;
}
url_request_->FollowDeferredRedirect();
}
void URLLoaderImpl::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
NOTIMPLEMENTED();
}
void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request,
const net::RedirectInfo& redirect_info,
bool* defer_redirect) {
DCHECK(url_request == url_request_.get());
DCHECK(url_request->status().is_success());
// Send the redirect response to the client, allowing them to inspect it and
// optionally follow the redirect.
*defer_redirect = true;
scoped_refptr<ResourceResponse> response = new ResourceResponse();
PopulateResourceResponse(url_request_.get(), response.get());
if (report_raw_headers_) {
response->head.devtools_info = BuildDevToolsInfo(
*url_request_, raw_request_headers_, raw_response_headers_.get());
raw_request_headers_ = net::HttpRawRequestHeaders();
raw_response_headers_ = nullptr;
}
url_loader_client_->OnReceiveRedirect(redirect_info, response->head);
}
void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request,
int net_error) {
DCHECK(url_request == url_request_.get());
if (net_error != net::OK) {
NotifyCompleted(net_error);
// |this| may have been deleted.
return;
}
response_ = new ResourceResponse();
PopulateResourceResponse(url_request_.get(), response_.get());
if (report_raw_headers_) {
response_->head.devtools_info = BuildDevToolsInfo(
*url_request_, raw_request_headers_, raw_response_headers_.get());
raw_request_headers_ = net::HttpRawRequestHeaders();
raw_response_headers_ = nullptr;
}
mojo::DataPipe data_pipe(kDefaultAllocationSize);
response_body_stream_ = std::move(data_pipe.producer_handle);
consumer_handle_ = std::move(data_pipe.consumer_handle);
peer_closed_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::Bind(&URLLoaderImpl::OnResponseBodyStreamClosed,
base::Unretained(this)));
peer_closed_handle_watcher_.ArmOrNotify();
writable_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&URLLoaderImpl::OnResponseBodyStreamReady,
base::Unretained(this)));
if (!(options_ & mojom::kURLLoadOptionSniffMimeType) ||
!ShouldSniffContent(url_request_.get(), response_.get()))
SendResponseToClient();
// Start reading...
ReadMore();
}
void URLLoaderImpl::ReadMore() {
// Once the MIME type is sniffed, all data is sent as soon as it is read from
// the network.
DCHECK(consumer_handle_.is_valid() || !pending_write_);
if (!pending_write_.get()) {
// TODO: we should use the abstractions in MojoAsyncResourceHandler.
pending_write_buffer_offset_ = 0;
MojoResult result = network::NetToMojoPendingBuffer::BeginWrite(
&response_body_stream_, &pending_write_, &pending_write_buffer_size_);
if (result != MOJO_RESULT_OK && result != MOJO_RESULT_SHOULD_WAIT) {
// The response body stream is in a bad state. Bail.
// TODO: How should this be communicated to our client?
writable_handle_watcher_.Cancel();
response_body_stream_.reset();
DeleteIfNeeded();
return;
}
DCHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()),
pending_write_buffer_size_);
if (consumer_handle_.is_valid()) {
DCHECK_GE(pending_write_buffer_size_,
static_cast<uint32_t>(net::kMaxBytesToSniff));
}
if (result == MOJO_RESULT_SHOULD_WAIT) {
// The pipe is full. We need to wait for it to have more space.
writable_handle_watcher_.ArmOrNotify();
return;
}
}
auto buf = base::MakeRefCounted<network::NetToMojoIOBuffer>(
pending_write_.get(), pending_write_buffer_offset_);
int bytes_read;
url_request_->Read(buf.get(),
static_cast<int>(pending_write_buffer_size_ -
pending_write_buffer_offset_),
&bytes_read);
if (url_request_->status().is_io_pending()) {
// Wait for OnReadCompleted.
} else if (url_request_->status().is_success() && bytes_read > 0) {
DidRead(static_cast<uint32_t>(bytes_read), true);
} else {
writable_handle_watcher_.Cancel();
CompletePendingWrite();
// Close body pipe.
response_body_stream_.reset();
NotifyCompleted(url_request_->status().ToNetError());
// |this| may have been deleted.
return;
}
}
void URLLoaderImpl::DidRead(uint32_t num_bytes, bool completed_synchronously) {
pending_write_buffer_offset_ += num_bytes;
DCHECK(url_request_->status().is_success());
bool complete_read = true;
if (consumer_handle_.is_valid()) {
const std::string& type_hint = response_->head.mime_type;
std::string new_type;
bool made_final_decision = net::SniffMimeType(
pending_write_->buffer(), pending_write_buffer_offset_,
url_request_->url(), type_hint, &new_type);
// SniffMimeType() returns false if there is not enough data to determine
// the mime type. However, even if it returns false, it returns a new type
// that is probably better than the current one.
response_->head.mime_type.assign(new_type);
if (made_final_decision) {
SendResponseToClient();
} else {
complete_read = false;
}
}
if (complete_read) {
CompletePendingWrite();
}
if (completed_synchronously) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&URLLoaderImpl::ReadMore,
weak_ptr_factory_.GetWeakPtr()));
} else {
ReadMore();
}
}
void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request,
int bytes_read) {
DCHECK(url_request == url_request_.get());
if (!url_request->status().is_success()) {
writable_handle_watcher_.Cancel();
CompletePendingWrite();
// This closes the data pipe.
// TODO(mmenke): Should NotifyCompleted close the data pipe itself instead?
response_body_stream_.reset();
NotifyCompleted(url_request_->status().ToNetError());
// |this| may have been deleted.
return;
}
DidRead(static_cast<uint32_t>(bytes_read), false);
}
base::WeakPtr<URLLoaderImpl> URLLoaderImpl::GetWeakPtrForTests() {
return weak_ptr_factory_.GetWeakPtr();
}
void URLLoaderImpl::NotifyCompleted(int error_code) {
if (consumer_handle_.is_valid())
SendResponseToClient();
ResourceRequestCompletionStatus request_complete_data;
request_complete_data.error_code = error_code;
request_complete_data.exists_in_cache =
url_request_->response_info().was_cached;
request_complete_data.completion_time = base::TimeTicks::Now();
request_complete_data.encoded_data_length =
url_request_->GetTotalReceivedBytes();
request_complete_data.encoded_body_length = url_request_->GetRawBodyBytes();
request_complete_data.decoded_body_length = total_written_bytes_;
url_loader_client_->OnComplete(request_complete_data);
DeleteIfNeeded();
}
void URLLoaderImpl::OnConnectionError() {
connected_ = false;
DeleteIfNeeded();
}
void URLLoaderImpl::OnResponseBodyStreamClosed(MojoResult result) {
url_request_.reset();
response_body_stream_.reset();
pending_write_ = nullptr;
DeleteIfNeeded();
}
void URLLoaderImpl::OnResponseBodyStreamReady(MojoResult result) {
// TODO: Handle a bad |result| value.
DCHECK_EQ(result, MOJO_RESULT_OK);
ReadMore();
}
void URLLoaderImpl::DeleteIfNeeded() {
bool has_data_pipe = pending_write_.get() || response_body_stream_.is_valid();
if (!connected_ && !has_data_pipe)
delete this;
}
void URLLoaderImpl::SendResponseToClient() {
base::Optional<net::SSLInfo> ssl_info;
if (options_ & mojom::kURLLoadOptionSendSSLInfo)
ssl_info = url_request_->ssl_info();
mojom::DownloadedTempFilePtr downloaded_file_ptr;
url_loader_client_->OnReceiveResponse(response_->head, ssl_info,
std::move(downloaded_file_ptr));
net::IOBufferWithSize* metadata =
url_request_->response_info().metadata.get();
if (metadata) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(metadata->data());
url_loader_client_->OnReceiveCachedMetadata(
std::vector<uint8_t>(data, data + metadata->size()));
}
url_loader_client_->OnStartLoadingResponseBody(std::move(consumer_handle_));
response_ = nullptr;
}
void URLLoaderImpl::CompletePendingWrite() {
response_body_stream_ =
pending_write_->Complete(pending_write_buffer_offset_);
pending_write_ = nullptr;
total_written_bytes_ += pending_write_buffer_offset_;
}
void URLLoaderImpl::SetRawResponseHeaders(
scoped_refptr<const net::HttpResponseHeaders> headers) {
raw_response_headers_ = headers;
}
} // namespace content