blob: d4bde56ceed35b4485e012ff0516bed87617dfd3 [file] [log] [blame]
// Copyright 2019 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/web_package/web_bundle_reader.h"
#include <limits>
#include "base/check_op.h"
#include "base/numerics/safe_math.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "content/browser/web_package/web_bundle_blob_data_source.h"
#include "content/browser/web_package/web_bundle_source.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "mojo/public/cpp/system/file_data_source.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "net/base/url_util.h"
#include "third_party/blink/public/common/web_package/web_package_request_matcher.h"
namespace content {
WebBundleReader::SharedFile::SharedFile(
std::unique_ptr<WebBundleSource> source) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](std::unique_ptr<WebBundleSource> source)
-> std::unique_ptr<base::File> { return source->OpenFile(); },
std::move(source)),
base::BindOnce(&SharedFile::SetFile, base::RetainedRef(this)));
}
void WebBundleReader::SharedFile::DuplicateFile(
base::OnceCallback<void(base::File)> callback) {
// Basically this interface expects this method is called at most once. Have
// a DCHECK for the case that does not work for a clear reason, just in case.
// The call site also have another DCHECK for external callers not to cause
// such problematic cases.
DCHECK(duplicate_callback_.is_null());
duplicate_callback_ = std::move(callback);
if (file_)
SetFile(std::move(file_));
}
base::File* WebBundleReader::SharedFile::operator->() {
DCHECK(file_);
return file_.get();
}
WebBundleReader::SharedFile::~SharedFile() {
// Move the last reference to |file_| that leads an internal blocking call
// that is not permitted here.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce([](std::unique_ptr<base::File> file) {},
std::move(file_)));
}
void WebBundleReader::SharedFile::SetFile(std::unique_ptr<base::File> file) {
file_ = std::move(file);
if (duplicate_callback_.is_null())
return;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](base::File* file) -> base::File { return file->Duplicate(); },
file_.get()),
std::move(duplicate_callback_));
}
class WebBundleReader::SharedFileDataSource final
: public mojo::DataPipeProducer::DataSource {
public:
SharedFileDataSource(scoped_refptr<WebBundleReader::SharedFile> file,
uint64_t offset,
uint64_t length)
: file_(std::move(file)), offset_(offset), length_(length) {
error_ = mojo::FileDataSource::ConvertFileErrorToMojoResult(
(*file_)->error_details());
// base::File::Read takes int64_t as an offset. So, offset + length should
// not overflow in int64_t.
uint64_t max_offset;
if (!base::CheckAdd(offset, length).AssignIfValid(&max_offset) ||
(std::numeric_limits<int64_t>::max() < max_offset)) {
error_ = MOJO_RESULT_INVALID_ARGUMENT;
}
}
private:
// Implements mojo::DataPipeProducer::DataSource. Following methods are called
// on a blockable sequenced task runner.
uint64_t GetLength() const override { return length_; }
ReadResult Read(uint64_t offset, base::span<char> buffer) override {
ReadResult result;
result.result = error_;
if (length_ < offset)
result.result = MOJO_RESULT_INVALID_ARGUMENT;
if (result.result != MOJO_RESULT_OK)
return result;
uint64_t readable_size = length_ - offset;
uint64_t writable_size = buffer.size();
uint64_t copyable_size =
std::min(std::min(readable_size, writable_size),
static_cast<uint64_t>(std::numeric_limits<int>::max()));
int bytes_read =
(*file_)->Read(offset_ + offset, buffer.data(), copyable_size);
if (bytes_read < 0) {
result.result = mojo::FileDataSource::ConvertFileErrorToMojoResult(
(*file_)->GetLastFileError());
} else {
result.bytes_read = bytes_read;
}
return result;
}
scoped_refptr<WebBundleReader::SharedFile> file_;
MojoResult error_;
const uint64_t offset_;
const uint64_t length_;
DISALLOW_COPY_AND_ASSIGN(SharedFileDataSource);
};
WebBundleReader::WebBundleReader(std::unique_ptr<WebBundleSource> source)
: source_(std::move(source)),
parser_(std::make_unique<data_decoder::SafeWebBundleParser>()),
file_(base::MakeRefCounted<SharedFile>(source_->Clone())) {
DCHECK(source_->is_trusted_file() || source_->is_file());
}
WebBundleReader::WebBundleReader(
std::unique_ptr<WebBundleSource> source,
int64_t content_length,
mojo::ScopedDataPipeConsumerHandle outer_response_body,
network::mojom::URLLoaderClientEndpointsPtr endpoints,
BrowserContext::BlobContextGetter blob_context_getter)
: source_(std::move(source)),
parser_(std::make_unique<data_decoder::SafeWebBundleParser>()) {
DCHECK(source_->is_network());
mojo::PendingRemote<web_package::mojom::BundleDataSource> pending_remote;
blob_data_source_ = std::make_unique<WebBundleBlobDataSource>(
content_length, std::move(outer_response_body), std::move(endpoints),
std::move(blob_context_getter));
blob_data_source_->AddReceiver(
pending_remote.InitWithNewPipeAndPassReceiver());
parser_->OpenDataSource(std::move(pending_remote));
}
WebBundleReader::~WebBundleReader() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void WebBundleReader::ReadMetadata(MetadataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kInitial);
if (!blob_data_source_) {
DCHECK(source_->is_trusted_file() || source_->is_file());
file_->DuplicateFile(base::BindOnce(&WebBundleReader::ReadMetadataInternal,
this, std::move(callback)));
return;
}
DCHECK(source_->is_network());
parser_->ParseMetadata(base::BindOnce(&WebBundleReader::OnMetadataParsed,
base::Unretained(this),
std::move(callback)));
}
void WebBundleReader::ReadResponse(
const network::ResourceRequest& resource_request,
const std::string& accept_langs,
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
auto it = entries_.find(net::SimplifyUrlForRequest(resource_request.url));
if (it == entries_.end() || it->second->response_locations.empty()) {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback), nullptr,
web_package::mojom::BundleResponseParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
"Not found in Web Bundle file.")));
return;
}
const web_package::mojom::BundleIndexValuePtr& entry = it->second;
size_t response_index = 0;
if (!entry->variants_value.empty()) {
// Select the best variant for the request.
blink::WebPackageRequestMatcher matcher(resource_request.headers,
accept_langs);
auto found = matcher.FindBestMatchingIndex(entry->variants_value);
if (!found || *found >= entry->response_locations.size()) {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback), nullptr,
web_package::mojom::BundleResponseParseError::New(
web_package::mojom::BundleParseErrorType::
kParserInternalError,
"Cannot find a response that matches request headers.")));
return;
}
response_index = *found;
}
auto response_location = entry->response_locations[response_index].Clone();
if (state_ == State::kDisconnected) {
// Try reconnecting, if not attempted yet.
if (pending_read_responses_.empty())
Reconnect();
pending_read_responses_.emplace_back(std::move(response_location),
std::move(callback));
return;
}
ReadResponseInternal(std::move(response_location), std::move(callback));
}
void WebBundleReader::ReadResponseInternal(
web_package::mojom::BundleResponseLocationPtr location,
ResponseCallback callback) {
parser_->ParseResponse(
location->offset, location->length,
base::BindOnce(&WebBundleReader::OnResponseParsed, base::Unretained(this),
std::move(callback)));
}
void WebBundleReader::Reconnect() {
DCHECK(!parser_);
parser_ = std::make_unique<data_decoder::SafeWebBundleParser>();
if (!blob_data_source_) {
DCHECK(source_->is_trusted_file() || source_->is_file());
file_->DuplicateFile(
base::BindOnce(&WebBundleReader::ReconnectForFile, this));
return;
}
DCHECK(source_->is_network());
mojo::PendingRemote<web_package::mojom::BundleDataSource> pending_remote;
blob_data_source_->AddReceiver(
pending_remote.InitWithNewPipeAndPassReceiver());
parser_->OpenDataSource(std::move(pending_remote));
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&WebBundleReader::DidReconnect, this,
base::nullopt /* error */));
}
void WebBundleReader::ReconnectForFile(base::File file) {
base::File::Error file_error = parser_->OpenFile(std::move(file));
base::Optional<std::string> error;
if (file_error != base::File::FILE_OK)
error = base::File::ErrorToString(file_error);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&WebBundleReader::DidReconnect, this, std::move(error)));
}
void WebBundleReader::DidReconnect(base::Optional<std::string> error) {
DCHECK_EQ(state_, State::kDisconnected);
DCHECK(parser_);
auto read_tasks = std::move(pending_read_responses_);
if (error) {
for (auto& pair : read_tasks) {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(std::move(pair.second), nullptr,
web_package::mojom::BundleResponseParseError::New(
web_package::mojom::BundleParseErrorType::
kParserInternalError,
*error)));
}
return;
}
state_ = State::kMetadataReady;
parser_->SetDisconnectCallback(base::BindOnce(
&WebBundleReader::OnParserDisconnected, base::Unretained(this)));
for (auto& pair : read_tasks)
ReadResponseInternal(std::move(pair.first), std::move(pair.second));
}
void WebBundleReader::ReadResponseBody(
web_package::mojom::BundleResponsePtr response,
mojo::ScopedDataPipeProducerHandle producer_handle,
BodyCompletionCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
if (!blob_data_source_) {
DCHECK(source_->is_trusted_file() || source_->is_file());
auto data_producer =
std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle));
mojo::DataPipeProducer* raw_producer = data_producer.get();
raw_producer->Write(
std::make_unique<SharedFileDataSource>(file_, response->payload_offset,
response->payload_length),
base::BindOnce(
[](std::unique_ptr<mojo::DataPipeProducer> producer,
BodyCompletionCallback callback, MojoResult result) {
std::move(callback).Run(
result == MOJO_RESULT_OK ? net::OK : net::ERR_UNEXPECTED);
},
std::move(data_producer), std::move(callback)));
return;
}
DCHECK(source_->is_network());
blob_data_source_->ReadToDataPipe(
response->payload_offset, response->payload_length,
std::move(producer_handle), std::move(callback));
}
bool WebBundleReader::HasEntry(const GURL& url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
return entries_.contains(net::SimplifyUrlForRequest(url));
}
const GURL& WebBundleReader::GetPrimaryURL() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
return primary_url_;
}
const WebBundleSource& WebBundleReader::source() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return *source_;
}
void WebBundleReader::ReadMetadataInternal(MetadataCallback callback,
base::File file) {
DCHECK(source_->is_trusted_file() || source_->is_file());
base::File::Error error = parser_->OpenFile(std::move(file));
if (base::File::FILE_OK != error) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
web_package::mojom::BundleMetadataParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
GURL() /* fallback_url */, base::File::ErrorToString(error))));
} else {
parser_->ParseMetadata(base::BindOnce(&WebBundleReader::OnMetadataParsed,
base::Unretained(this),
std::move(callback)));
}
}
void WebBundleReader::OnMetadataParsed(
MetadataCallback callback,
web_package::mojom::BundleMetadataPtr metadata,
web_package::mojom::BundleMetadataParseErrorPtr error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, State::kInitial);
state_ = State::kMetadataReady;
parser_->SetDisconnectCallback(base::BindOnce(
&WebBundleReader::OnParserDisconnected, base::Unretained(this)));
if (metadata) {
primary_url_ = metadata->primary_url;
entries_ = std::move(metadata->requests);
}
std::move(callback).Run(std::move(error));
}
void WebBundleReader::OnResponseParsed(
ResponseCallback callback,
web_package::mojom::BundleResponsePtr response,
web_package::mojom::BundleResponseParseErrorPtr error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
std::move(callback).Run(std::move(response), std::move(error));
}
void WebBundleReader::OnParserDisconnected() {
DCHECK_EQ(state_, State::kMetadataReady);
state_ = State::kDisconnected;
parser_ = nullptr;
// Reconnection will be attempted on next ReadResponse() call.
}
} // namespace content