blob: a01491a03053f31f5633d94fbcc32530784eb8cd [file] [log] [blame]
// Copyright 2018 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 "fuchsia/http/url_loader_impl.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/message_loop/message_loop_current.h"
#include "base/task/post_task.h"
#include "net/base/chunked_upload_data_stream.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/redirect_info.h"
namespace oldhttp = ::fuchsia::net::oldhttp;
namespace {
// Capacity, in bytes, for buffers used to move data from client requests or
// server responses.
const size_t kReadCapacity = 1024;
// The number of active requests. Used for testing.
int g_active_requests = 0;
// Converts |buffer| into a URLBody with the body set to a buffer. Returns
// nullptr when an error occurs.
oldhttp::URLBodyPtr CreateURLBodyFromBuffer(net::GrowableIOBuffer* buffer) {
oldhttp::URLBodyPtr body = oldhttp::URLBody::New();
// The response buffer size is exactly the offset.
size_t total_size = buffer->offset();
::fuchsia::mem::Buffer mem_buffer;
mem_buffer.size = total_size;
zx_status_t result =
zx::vmo::create(total_size, ZX_VMO_NON_RESIZABLE, &mem_buffer.vmo);
if (result != ZX_OK) {
ZX_DLOG(WARNING, result) << "zx_vmo_create";
return nullptr;
}
result = mem_buffer.vmo.write(buffer->StartOfBuffer(), 0, total_size);
if (result != ZX_OK) {
ZX_DLOG(WARNING, result) << "zx_vmo_write";
return nullptr;
}
body->set_buffer(std::move(mem_buffer));
return body;
}
int NetErrorToHttpError(int net_error) {
// TODO(https://crbug.com/875533): Convert the Chromium //net error to their
// Fuchsia counterpart.
return net_error;
}
oldhttp::HttpErrorPtr BuildError(int net_error) {
if (net_error == net::OK) {
return nullptr;
}
oldhttp::HttpErrorPtr error = oldhttp::HttpError::New();
error->code = NetErrorToHttpError(net_error);
error->description = net::ErrorToString(net_error);
return error;
}
std::unique_ptr<net::UploadDataStream> UploadDataStreamFromZxSocket(
zx::socket stream) {
// TODO(http://crbug.com/875534): Write a ZxStreamUploadStream class.
std::unique_ptr<net::ChunkedUploadDataStream> upload_stream =
std::make_unique<net::ChunkedUploadDataStream>(0);
char buffer[kReadCapacity];
size_t size = 0;
zx_status_t result = ZX_OK;
while (true) {
result = stream.read(0, buffer, kReadCapacity, &size);
if (result != ZX_OK) {
ZX_DLOG(WARNING, result) << "zx_socket_read";
return nullptr;
}
if (size < kReadCapacity) {
upload_stream->AppendData(buffer, size, false);
break;
}
upload_stream->AppendData(buffer, size, true);
}
return upload_stream;
}
std::unique_ptr<net::UploadDataStream> UploadDataStreamFromMemBuffer(
fuchsia::mem::Buffer mem_buffer) {
// TODO(http://crbug.com/875534): Write a ZxMemBufferUploadStream class.
std::unique_ptr<net::ChunkedUploadDataStream> upload_stream =
std::make_unique<net::ChunkedUploadDataStream>(0);
char buffer[kReadCapacity];
size_t size = mem_buffer.size;
size_t offset = 0;
zx_status_t result = ZX_OK;
while (offset != size) {
size_t length = std::min(size - offset, kReadCapacity);
result = mem_buffer.vmo.read(buffer, offset, length);
if (result != ZX_OK) {
ZX_DLOG(WARNING, result) << "zx_vmo_read";
return nullptr;
}
upload_stream->AppendData(buffer, length, false);
offset += length;
}
return upload_stream;
}
} // namespace
URLLoaderImpl::URLLoaderImpl(std::unique_ptr<net::URLRequestContext> context,
fidl::InterfaceRequest<oldhttp::URLLoader> request)
: binding_(this, std::move(request)),
context_(std::move(context)),
buffer_(new net::GrowableIOBuffer()),
write_watch_(FROM_HERE) {
binding_.set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
<< " URLLoader disconnected.";
delete this;
});
g_active_requests++;
}
URLLoaderImpl::~URLLoaderImpl() {
g_active_requests--;
}
int URLLoaderImpl::GetNumActiveRequestsForTests() {
return g_active_requests;
}
void URLLoaderImpl::Start(oldhttp::URLRequest request, Callback callback) {
if (net_request_) {
callback(BuildResponse(net::ERR_IO_PENDING));
return;
}
done_callback_ = std::move(callback);
net_error_ = net::OK;
// Create the URLRequest and set this object as the delegate.
net_request_ = context_->CreateRequest(GURL(request.url),
net::RequestPriority::MEDIUM, this);
net_request_->set_method(request.method);
// Set extra headers.
if (request.headers) {
for (oldhttp::HttpHeader header : *(request.headers)) {
net_request_->SetExtraRequestHeaderByName(header.name, header.value,
false);
}
}
if (request.cache_mode == oldhttp::CacheMode::BYPASS_CACHE) {
net_request_->SetExtraRequestHeaderByName("Cache-Control", "nocache",
false);
}
std::unique_ptr<net::UploadDataStream> upload_stream;
// Set body.
if (request.body) {
if (request.body->is_stream()) {
upload_stream =
UploadDataStreamFromZxSocket(std::move(request.body->stream()));
} else {
upload_stream =
UploadDataStreamFromMemBuffer(std::move(request.body->buffer()));
}
if (!upload_stream) {
std::move(done_callback_)(BuildResponse(net::ERR_ACCESS_DENIED));
return;
}
net_request_->set_upload(std::move(upload_stream));
}
auto_follow_redirects_ = request.auto_follow_redirects;
response_body_mode_ = request.response_body_mode;
// Start the request.
net_request_->Start();
}
void URLLoaderImpl::FollowRedirect(Callback callback) {
if (!net_request_ || auto_follow_redirects_ ||
!net_request_->is_redirecting()) {
callback(BuildResponse(net::ERR_INVALID_HANDLE));
}
done_callback_ = std::move(callback);
net_request_->FollowDeferredRedirect(base::nullopt /* removed_headers */,
base::nullopt /* modified_headers */);
}
void URLLoaderImpl::QueryStatus(QueryStatusCallback callback) {
oldhttp::URLLoaderStatus status;
if (!net_request_) {
status.is_loading = false;
} else if (net_request_->is_pending() || net_request_->is_redirecting()) {
status.is_loading = true;
} else {
status.is_loading = false;
status.error = BuildError(net_error_);
}
callback(std::move(status));
}
void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* request,
const net::RedirectInfo& redirect_info,
bool* defer_redirect) {
DCHECK_EQ(net_request_.get(), request);
// Follow redirect depending on policy.
*defer_redirect = !auto_follow_redirects_;
if (!auto_follow_redirects_) {
oldhttp::URLResponse response = BuildResponse(net::OK);
response.redirect_method = redirect_info.new_method;
response.redirect_url = redirect_info.new_url.spec();
response.redirect_referrer = redirect_info.new_referrer;
std::move(done_callback_)(std::move(response));
}
}
void URLLoaderImpl::OnAuthRequired(net::URLRequest* request,
const net::AuthChallengeInfo& auth_info) {
NOTIMPLEMENTED();
DCHECK_EQ(net_request_.get(), request);
request->CancelAuth();
}
void URLLoaderImpl::OnCertificateRequested(
net::URLRequest* request,
net::SSLCertRequestInfo* cert_request_info) {
NOTIMPLEMENTED();
DCHECK_EQ(net_request_.get(), request);
request->ContinueWithCertificate(nullptr, nullptr);
}
void URLLoaderImpl::OnSSLCertificateError(net::URLRequest* request,
int net_error,
const net::SSLInfo& ssl_info,
bool fatal) {
NOTIMPLEMENTED();
DCHECK_EQ(net_request_.get(), request);
request->Cancel();
}
void URLLoaderImpl::OnResponseStarted(net::URLRequest* request, int net_error) {
DCHECK_EQ(net_request_.get(), request);
net_error_ = net_error;
// Return early if the request failed.
if (net_error_ != net::OK) {
std::move(done_callback_)(BuildResponse(net_error_));
return;
}
// In stream mode, call the callback now and write to the socket.
if (response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
response_body_mode_ == oldhttp::ResponseBodyMode::BUFFER_OR_STREAM) {
zx::socket read_socket;
zx_status_t result = zx::socket::create(0, &read_socket, &write_socket_);
if (result != ZX_OK) {
ZX_DLOG(WARNING, result) << "zx_socket_create";
std::move(done_callback_)(BuildResponse(net::ERR_INSUFFICIENT_RESOURCES));
return;
}
oldhttp::URLResponse response = BuildResponse(net::OK);
response.body = oldhttp::URLBody::New();
response.body->set_stream(std::move(read_socket));
std::move(done_callback_)(std::move(response));
}
// In stream mode, the buffer is used as a temporary buffer to write to the
// socket. In buffer mode, it is expanded as more of the response is read.
buffer_->SetCapacity(kReadCapacity);
ReadNextBuffer();
}
void URLLoaderImpl::OnReadCompleted(net::URLRequest* request, int bytes_read) {
DCHECK_EQ(net_request_.get(), request);
if (WriteResponseBytes(bytes_read)) {
ReadNextBuffer();
}
}
void URLLoaderImpl::OnZxHandleSignalled(zx_handle_t handle,
zx_signals_t signals) {
// We should never have to process signals we didn't ask for.
DCHECK((ZX_CHANNEL_WRITABLE | ZX_CHANNEL_PEER_CLOSED) & signals);
DCHECK_GT(buffered_bytes_, 0);
if (signals & ZX_CHANNEL_PEER_CLOSED) {
return;
}
if (WriteResponseBytes(buffered_bytes_))
ReadNextBuffer();
buffered_bytes_ = 0;
}
void URLLoaderImpl::ReadNextBuffer() {
int net_result;
do {
net_result = net_request_->Read(buffer_.get(), kReadCapacity);
if (net_result == net::ERR_IO_PENDING) {
return;
}
} while (WriteResponseBytes(net_result));
}
bool URLLoaderImpl::WriteResponseBytes(int result) {
if (result < 0) {
// Signal read error back to the client.
if (write_socket_) {
DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
response_body_mode_ ==
oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
// There is no need to check the return value of this call as there is no
// way to recover from a failed socket close.
write_socket_ = zx::socket();
} else {
DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
std::move(done_callback_)(BuildResponse(result));
}
return false;
}
if (result == 0) {
// Read complete.
if (write_socket_) {
DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
response_body_mode_ ==
oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
// In socket mode, attempt to shut down the socket and close it.
write_socket_.shutdown(ZX_SOCKET_SHUTDOWN_WRITE);
write_socket_ = zx::socket();
} else {
DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
// In buffer mode, build the response and call the callback.
oldhttp::URLBodyPtr body = CreateURLBodyFromBuffer(buffer_.get());
if (body) {
oldhttp::URLResponse response = BuildResponse(net::OK);
response.body = std::move(body);
std::move(done_callback_)(std::move(response));
} else {
std::move(done_callback_)(
BuildResponse(net::ERR_INSUFFICIENT_RESOURCES));
}
}
return false;
}
// Write data to the response buffer or socket.
if (write_socket_) {
DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
response_body_mode_ == oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
// In socket mode, attempt to write to the socket.
zx_status_t status =
write_socket_.write(0, buffer_->data(), result, nullptr);
if (status == ZX_ERR_SHOULD_WAIT) {
// Wait until the socket is writable again.
buffered_bytes_ = result;
base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
write_socket_.get(), false /* persistent */,
ZX_SOCKET_WRITABLE | ZX_SOCKET_PEER_CLOSED, &write_watch_, this);
return false;
}
if (status != ZX_OK) {
// Something went wrong, attempt to shut down the socket and close it.
ZX_DLOG(WARNING, status) << "zx_socket_write";
write_socket_ = zx::socket();
return false;
}
} else {
DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
// In buffer mode, expand the buffer.
buffer_->SetCapacity(buffer_->capacity() + result);
buffer_->set_offset(buffer_->offset() + result);
}
return true;
}
oldhttp::URLResponse URLLoaderImpl::BuildResponse(int net_error) {
oldhttp::URLResponse response;
response.error = BuildError(net_error);
if (response.error) {
return response;
}
if (net_request_->url().is_valid()) {
response.url = net_request_->url().spec();
}
response.status_code = net_request_->GetResponseCode();
net::HttpResponseHeaders* response_headers = net_request_->response_headers();
if (response_headers) {
response.status_line = response_headers->GetStatusLine();
std::string mime_type;
if (response_headers->GetMimeType(&mime_type)) {
response.mime_type = mime_type;
}
std::string charset;
if (response_headers->GetCharset(&charset)) {
response.charset = charset;
}
size_t iter = 0;
std::string header_name;
std::string header_value;
while (response_headers->EnumerateHeaderLines(&iter, &header_name,
&header_value)) {
oldhttp::HttpHeader header;
header.name = header_name;
header.value = header_value;
response.headers.push_back(header);
}
}
return response;
}