blob: 546f6f381901016dfd1faa2d580bba5875046d84 [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 "content/browser/android/content_url_loader_factory.h"
#include <string>
#include <vector>
#include "base/android/content_uri_utils.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/file_url_loader.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/file_data_pipe_producer.h"
#include "net/base/net_errors.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
// TODO(eroman): Add unit-tests for "X-Chrome-intent-type"
// (see url_request_content_job_unittest.cc).
// TODO(eroman): Remove duplication with file_url_loader_factory.cc (notably
// Range header parsing).
namespace content {
namespace {
constexpr size_t kDefaultContentUrlPipeSize = 65536;
// Assigns the byte range that has been requested based on the Range header.
// This assumes the simplest form of the Range header using a single range.
// If no byte range was specified, the output range will cover the entire file.
bool GetRequestedByteRange(const network::ResourceRequest& request,
size_t content_size,
size_t* first_byte_to_send,
size_t* total_bytes_to_send) {
*first_byte_to_send = 0;
*total_bytes_to_send = content_size;
std::string range_header;
std::vector<net::HttpByteRange> ranges;
if (!request.headers.GetHeader(net::HttpRequestHeaders::kRange,
&range_header) ||
!net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
return true;
}
// Only handle a simple Range header for a single range.
net::HttpByteRange byte_range = ranges[0];
if (ranges.size() != 1 || !byte_range.ComputeBounds(content_size))
return false;
*first_byte_to_send = static_cast<size_t>(byte_range.first_byte_position());
*total_bytes_to_send = static_cast<size_t>(byte_range.last_byte_position()) -
*first_byte_to_send + 1;
return true;
}
// Gets the mimetype for |content_path| either by asking the content provider,
// or by using the special Chrome request header X-Chrome-intent-type.
void GetMimeType(const network::ResourceRequest& request,
const base::FilePath& content_path,
std::string* out_mime_type) {
out_mime_type->clear();
std::string intent_type_header;
if ((request.resource_type == content::RESOURCE_TYPE_MAIN_FRAME) &&
request.headers.GetHeader("X-Chrome-intent-type", &intent_type_header)) {
*out_mime_type = intent_type_header;
}
if (out_mime_type->empty())
*out_mime_type = base::GetContentUriMimeType(content_path);
}
class ContentURLLoader : public network::mojom::URLLoader {
public:
static void CreateAndStart(
const network::ResourceRequest& request,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info) {
// Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
// bindings are alive - essentially until either the client gives up or all
// file data has been sent to it.
auto* content_url_loader = new ContentURLLoader;
content_url_loader->Start(request, std::move(loader),
std::move(client_info));
}
// network::mojom::URLLoader:
void FollowRedirect(
const base::Optional<std::vector<std::string>>&
to_be_removed_request_headers,
const base::Optional<net::HttpRequestHeaders>& modified_request_headers,
const base::Optional<GURL>& new_url) override {}
void ProceedWithResponse() override {}
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
private:
ContentURLLoader() : binding_(this) {}
~ContentURLLoader() override = default;
void Start(const network::ResourceRequest& request,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info) {
network::ResourceResponseHead head;
head.request_start = head.response_start = base::TimeTicks::Now();
binding_.Bind(std::move(loader));
binding_.set_connection_error_handler(base::BindOnce(
&ContentURLLoader::OnConnectionError, base::Unretained(this)));
network::mojom::URLLoaderClientPtr client;
client.Bind(std::move(client_info));
DCHECK(request.url.SchemeIs("content"));
base::FilePath path = base::FilePath(request.url.spec());
// Get the file length.
base::File::Info info;
if (!base::GetFileInfo(path, &info))
return CompleteWithFailure(std::move(client), net::ERR_FILE_NOT_FOUND);
size_t first_byte_to_send;
size_t total_bytes_to_send;
if (!GetRequestedByteRange(request, info.size, &first_byte_to_send,
&total_bytes_to_send)) {
return CompleteWithFailure(std::move(client),
net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
}
mojo::DataPipe pipe(kDefaultContentUrlPipeSize);
if (!pipe.consumer_handle.is_valid()) {
return CompleteWithFailure(std::move(client), net::ERR_FAILED);
}
base::File file = base::OpenContentUriForRead(path);
if (!file.IsValid()) {
return CompleteWithFailure(
std::move(client), net::FileErrorToNetError(file.error_details()));
return;
}
total_bytes_written_ = total_bytes_to_send;
head.content_length = base::saturated_cast<int64_t>(total_bytes_to_send);
// Set the mimetype of the response.
GetMimeType(request, path, &head.mime_type);
if (!head.mime_type.empty() && head.headers) {
head.headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
head.headers->AddHeader(
base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType,
head.mime_type.c_str()));
}
client->OnReceiveResponse(head);
client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
client_ = std::move(client);
if (total_bytes_to_send == 0) {
// There's no more data, so we're already done.
OnFileWritten(MOJO_RESULT_OK);
return;
}
// In case of a range request, seek to the appropriate position before
// sending the remaining bytes asynchronously. Under normal conditions
// (i.e., no range request) this Seek is effectively a no-op.
file.Seek(base::File::FROM_BEGIN, static_cast<int64_t>(first_byte_to_send));
data_producer_ = std::make_unique<mojo::FileDataPipeProducer>(
std::move(pipe.producer_handle), /*observer=*/nullptr);
data_producer_->WriteFromFile(
std::move(file), total_bytes_to_send,
base::BindOnce(&ContentURLLoader::OnFileWritten,
base::Unretained(this)));
}
void CompleteWithFailure(network::mojom::URLLoaderClientPtr client,
net::Error net_error) {
client->OnComplete(network::URLLoaderCompletionStatus(net_error));
MaybeDeleteSelf();
}
void OnConnectionError() {
binding_.Close();
MaybeDeleteSelf();
}
void MaybeDeleteSelf() {
if (!binding_.is_bound() && !client_.is_bound())
delete this;
}
void OnFileWritten(MojoResult result) {
// All the data has been written now. Close the data pipe. The consumer will
// be notified that there will be no more data to read from now.
data_producer_.reset();
if (result == MOJO_RESULT_OK) {
network::URLLoaderCompletionStatus status(net::OK);
status.encoded_data_length = total_bytes_written_;
status.encoded_body_length = total_bytes_written_;
status.decoded_body_length = total_bytes_written_;
client_->OnComplete(status);
} else {
client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
}
client_.reset();
MaybeDeleteSelf();
}
std::unique_ptr<mojo::FileDataPipeProducer> data_producer_;
mojo::Binding<network::mojom::URLLoader> binding_;
network::mojom::URLLoaderClientPtr client_;
// In case of successful loads, this holds the total of bytes written.
// It is used to set some of the URLLoaderCompletionStatus data passed back
// to the URLLoaderClients (eg SimpleURLLoader).
size_t total_bytes_written_ = 0;
DISALLOW_COPY_AND_ASSIGN(ContentURLLoader);
};
} // namespace
ContentURLLoaderFactory::ContentURLLoaderFactory(
scoped_refptr<base::SequencedTaskRunner> task_runner)
: task_runner_(std::move(task_runner)) {}
ContentURLLoaderFactory::~ContentURLLoaderFactory() = default;
void ContentURLLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ContentURLLoader::CreateAndStart, request,
std::move(loader), client.PassInterface()));
}
void ContentURLLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest loader) {
bindings_.AddBinding(this, std::move(loader));
}
} // namespace content