blob: 5aca9b928824981c4ad0c7d50c2b67c16ad4f884 [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 "content/browser/web_package/web_bundle_reader.h"
#include "base/check_op.h"
#include "base/task/thread_pool.h"
#include "components/web_package/shared_file.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 "net/base/url_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace content {
WebBundleReader::WebBundleReader(std::unique_ptr<WebBundleSource> source)
: source_(std::move(source)),
parser_(std::make_unique<data_decoder::SafeWebBundleParser>(
/*base_url=*/absl::nullopt)) {
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>(
/*base_url=*/absl::nullopt)) {
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());
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](std::unique_ptr<WebBundleSource> source)
-> std::unique_ptr<base::File> { return source->OpenFile(); },
source_->Clone()),
base::BindOnce(&WebBundleReader::OnFileOpened, this,
std::move(callback)));
return;
}
DCHECK(source_->is_network());
parser_->ParseMetadata(
/*offset=*/-1,
base::BindOnce(&WebBundleReader::OnMetadataParsed, base::Unretained(this),
std::move(callback)));
}
void WebBundleReader::ReadResponse(
const network::ResourceRequest& resource_request,
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()) {
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;
}
auto response_location = it->second.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>(
/*base_url=*/absl::nullopt);
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,
absl::nullopt /* error */));
}
void WebBundleReader::ReconnectForFile(base::File file) {
base::File::Error file_error = parser_->OpenFile(std::move(file));
absl::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(absl::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(
file_->CreateDataSource(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));
}
std::vector<GURL> WebBundleReader::GetEntries() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitial);
std::vector<GURL> entries;
entries.reserve(entries_.size());
base::ranges::transform(entries_, std::back_inserter(entries),
[](const auto& entry) { return entry.first; });
return entries;
}
const absl::optional<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::OnFileOpened(MetadataCallback callback,
std::unique_ptr<base::File> file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(source_->is_trusted_file() || source_->is_file());
if (!file->IsValid()) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
web_package::mojom::BundleMetadataParseError::New(
web_package::mojom::BundleParseErrorType::kParserInternalError,
base::File::ErrorToString(file->error_details()))));
return;
}
file_ = base::MakeRefCounted<web_package::SharedFile>(std::move(file));
file_->DuplicateFile(base::BindOnce(&WebBundleReader::OnFileDuplicated, this,
std::move(callback)));
}
void WebBundleReader::OnFileDuplicated(MetadataCallback callback,
base::File file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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,
base::File::ErrorToString(error))));
} else {
parser_->ParseMetadata(
/*offset=*/-1,
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;
DCHECK(!primary_url_.has_value() || primary_url_->is_valid());
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