blob: 12897b4bd99a42737670388c3d8f3fd8a2b40f42 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/data_decoder/public/cpp/safe_web_bundle_parser.h"
#include <memory>
#include <optional>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "url/gurl.h"
namespace data_decoder {
namespace {
constexpr char kConnectionError[] =
"Cannot connect to the remote parser service";
void CloseFile(base::File file, base::OnceClosure close_callback) {
base::ThreadPool::PostTaskAndReply(
FROM_HERE, {base::MayBlock()},
base::BindOnce([](base::File file) { file.Close(); }, std::move(file)),
std::move(close_callback));
}
// The strategy that creates a BundleDataSource from a file.
class FileDataSourceStrategy : public DataSourceCreatingStrategy {
public:
explicit FileDataSourceStrategy(base::File file) : file_(std::move(file)) {}
FileDataSourceStrategy(const FileDataSourceStrategy&) = delete;
FileDataSourceStrategy& operator=(const FileDataSourceStrategy&) = delete;
~FileDataSourceStrategy() override {
if (file_.IsValid()) {
CloseFile(std::move(file_), base::DoNothing());
}
}
base::expected<void, std::string> ExpectReady() const override {
if (!file_.IsValid()) {
return base::unexpected(base::File::ErrorToString(file_.error_details()));
}
return base::ok();
}
mojo::PendingRemote<web_package::mojom::BundleDataSource> CreateDataSource(
web_package::mojom::WebBundleParserFactory* binder) override {
mojo::PendingRemote<web_package::mojom::BundleDataSource>
file_data_source_pending_remote;
binder->BindFileDataSource(
file_data_source_pending_remote.InitWithNewPipeAndPassReceiver(),
file_.Duplicate());
return file_data_source_pending_remote;
}
void Close(base::OnceClosure callback) override {
CloseFile(std::move(file_), std::move(callback));
}
private:
base::File file_;
};
void ReplyWithInternalError(
web_package::mojom::WebBundleParser::ParseIntegrityBlockCallback callback,
std::string error) {
std::move(callback).Run(
nullptr,
web_package::mojom::BundleIntegrityBlockParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
std::move(error)));
}
void ReplyWithInternalError(
web_package::mojom::WebBundleParser::ParseMetadataCallback callback,
std::string error) {
std::move(callback).Run(
nullptr,
web_package::mojom::BundleMetadataParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
std::move(error)));
}
void ReplyWithInternalError(
web_package::mojom::WebBundleParser::ParseResponseCallback callback,
std::string error) {
std::move(callback).Run(
nullptr,
web_package::mojom::BundleResponseParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
std::move(error)));
}
} // namespace
// static
std::unique_ptr<DataSourceCreatingStrategy>
SafeWebBundleParser::GetFileStrategy(base::File file) {
return std::make_unique<data_decoder::FileDataSourceStrategy>(
std::move(file));
}
SafeWebBundleParser::Connection::Connection() = default;
SafeWebBundleParser::Connection::~Connection() = default;
SafeWebBundleParser::SafeWebBundleParser(
std::optional<GURL> base_url,
std::unique_ptr<DataSourceCreatingStrategy> data_source_creator)
: data_source_creator_(std::move(data_source_creator)),
base_url_(std::move(base_url)) {}
SafeWebBundleParser::~SafeWebBundleParser() = default;
void SafeWebBundleParser::ParseIntegrityBlock(
web_package::mojom::WebBundleParser::ParseIntegrityBlockCallback callback) {
// This method is designed to be called once. So, allowing only once
// simultaneous request is fine enough.
if (!integrity_block_callback_.is_null()) {
ReplyWithInternalError(std::move(callback), kConnectionError);
return;
}
auto connection_status = ConnectIfNecessary();
if (!connection_status.has_value()) {
ReplyWithInternalError(std::move(callback), connection_status.error());
return;
}
integrity_block_callback_ = std::move(callback);
connection_->parser_->ParseIntegrityBlock(base::BindOnce(
&SafeWebBundleParser::OnIntegrityBlockParsed, base::Unretained(this)));
}
void SafeWebBundleParser::ParseMetadata(
std::optional<uint64_t> offset,
web_package::mojom::WebBundleParser::ParseMetadataCallback callback) {
// This method is designed to be called once. So, allowing only once
// simultaneous request is fine enough.
if (!metadata_callback_.is_null()) {
ReplyWithInternalError(std::move(callback), kConnectionError);
return;
}
auto connection_status = ConnectIfNecessary();
if (!connection_status.has_value()) {
ReplyWithInternalError(std::move(callback), connection_status.error());
return;
}
metadata_callback_ = std::move(callback);
connection_->parser_->ParseMetadata(
std::move(offset), base::BindOnce(&SafeWebBundleParser::OnMetadataParsed,
base::Unretained(this)));
}
void SafeWebBundleParser::ParseResponse(
uint64_t response_offset,
uint64_t response_length,
web_package::mojom::WebBundleParser::ParseResponseCallback callback) {
// This method allows simultaneous multiple requests. But if the unique ID
// overflows, and the previous request that owns the same ID hasn't finished,
// we just make the new request fail with kConnectionError.
if (response_callbacks_.contains(response_callback_next_id_)) {
ReplyWithInternalError(std::move(callback), kConnectionError);
return;
}
auto connection_status = ConnectIfNecessary();
if (!connection_status.has_value()) {
ReplyWithInternalError(std::move(callback), connection_status.error());
return;
}
size_t callback_id = response_callback_next_id_++;
response_callbacks_[callback_id] = std::move(callback);
connection_->parser_->ParseResponse(
response_offset, response_length,
base::BindOnce(&SafeWebBundleParser::OnResponseParsed,
base::Unretained(this), callback_id));
}
void SafeWebBundleParser::Close(base::OnceClosure callback) {
close_callback_ = std::move(callback);
if (is_connected()) {
connection_->parser_->Close(base::BindOnce(
&SafeWebBundleParser::CloseDataSourceCreator, base::Unretained(this)));
} else {
CloseDataSourceCreator();
}
}
web_package::mojom::WebBundleParserFactory* SafeWebBundleParser::GetFactory() {
CHECK(is_connected());
if (!connection_->factory_) {
connection_->data_decoder_.GetService()->BindWebBundleParserFactory(
connection_->factory_.BindNewPipeAndPassReceiver());
connection_->factory_.reset_on_disconnect();
}
return connection_->factory_.get();
}
base::expected<void, std::string> SafeWebBundleParser::ConnectIfNecessary() {
if (is_connected()) {
return base::ok();
}
RETURN_IF_ERROR(data_source_creator_->ExpectReady());
connection_ = std::make_unique<Connection>();
auto data_source = data_source_creator_->CreateDataSource(GetFactory());
GetFactory()->GetParserForDataSource(
connection_->parser_.BindNewPipeAndPassReceiver(), base_url_,
std::move(data_source));
connection_->parser_.set_disconnect_handler(base::BindOnce(
&SafeWebBundleParser::OnDisconnect, base::Unretained(this)));
return base::ok();
}
void SafeWebBundleParser::OnDisconnect() {
CHECK(connection_);
connection_.reset();
// Any of these callbacks could delete `this`, hence we need to make sure to
// not access any instance variables after we run any of these callbacks.
auto integrity_block_callback = std::move(integrity_block_callback_);
auto metadata_callback = std::move(metadata_callback_);
auto response_callbacks = std::exchange(response_callbacks_, {});
auto close_callback = std::move(close_callback_);
if (!integrity_block_callback.is_null()) {
ReplyWithInternalError(std::move(integrity_block_callback),
kConnectionError);
}
if (!metadata_callback.is_null()) {
ReplyWithInternalError(std::move(metadata_callback), kConnectionError);
}
for (auto& [callback_id, callback] : response_callbacks) {
ReplyWithInternalError(std::move(callback), kConnectionError);
}
if (!close_callback.is_null()) {
std::move(close_callback).Run();
}
}
void SafeWebBundleParser::OnIntegrityBlockParsed(
web_package::mojom::BundleIntegrityBlockPtr integrity_block,
web_package::mojom::BundleIntegrityBlockParseErrorPtr error) {
DCHECK(!integrity_block_callback_.is_null());
std::move(integrity_block_callback_)
.Run(std::move(integrity_block), std::move(error));
}
void SafeWebBundleParser::OnMetadataParsed(
web_package::mojom::BundleMetadataPtr metadata,
web_package::mojom::BundleMetadataParseErrorPtr error) {
DCHECK(!metadata_callback_.is_null());
std::move(metadata_callback_).Run(std::move(metadata), std::move(error));
}
void SafeWebBundleParser::OnResponseParsed(
size_t callback_id,
web_package::mojom::BundleResponsePtr response,
web_package::mojom::BundleResponseParseErrorPtr error) {
auto it = response_callbacks_.find(callback_id);
CHECK(it != response_callbacks_.end());
auto callback = std::move(it->second);
response_callbacks_.erase(it);
std::move(callback).Run(std::move(response), std::move(error));
}
void SafeWebBundleParser::CloseDataSourceCreator() {
data_source_creator_->Close(base::BindOnce(&SafeWebBundleParser::ReplyClosed,
weak_factory_.GetWeakPtr()));
}
void SafeWebBundleParser::ReplyClosed() {
CHECK(!close_callback_.is_null());
std::move(close_callback_).Run();
}
} // namespace data_decoder