Commit b2b335b7 authored by Takashi Toyoshima's avatar Takashi Toyoshima Committed by Commit Bot

BundledExchanges: SafeBundledExchangesParser and BundledExchangesReader

This patch implements SafeBundledExchangesParser and
BundledExchangesReader.

SafeBundledExchangesParser manages remote BundledExchangesParser
interface to communicate with data_decoder service in a reliable way.

BundledExchangesReader uses this trusted parser implementation
and provides functionalities to read BundledExchanges resource
in the browser process.

In this patch, BundledExchangesFactory uses the reader interface
to obtain the initial redirect URL.

Bug: 966753
Change-Id: I0a03237141bea9639a0cbeac4ae085a87fac6c08
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1712792Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682611}
parent bec05cc9
...@@ -1863,6 +1863,8 @@ jumbo_source_set("browser") { ...@@ -1863,6 +1863,8 @@ jumbo_source_set("browser") {
"web_contents/web_drag_utils_win.h", "web_contents/web_drag_utils_win.h",
"web_package/bundled_exchanges_factory.cc", "web_package/bundled_exchanges_factory.cc",
"web_package/bundled_exchanges_factory.h", "web_package/bundled_exchanges_factory.h",
"web_package/bundled_exchanges_reader.cc",
"web_package/bundled_exchanges_reader.h",
"web_package/bundled_exchanges_source.cc", "web_package/bundled_exchanges_source.cc",
"web_package/bundled_exchanges_source.h", "web_package/bundled_exchanges_source.h",
"web_package/prefetched_signed_exchange_cache.cc", "web_package/prefetched_signed_exchange_cache.cc",
......
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
#include "content/browser/web_package/bundled_exchanges_factory.h" #include "content/browser/web_package/bundled_exchanges_factory.h"
#include "base/bind.h"
#include "content/browser/loader/navigation_loader_interceptor.h" #include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/web_package/bundled_exchanges_reader.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
namespace { namespace {
...@@ -15,7 +17,7 @@ namespace { ...@@ -15,7 +17,7 @@ namespace {
class Interceptor final : public content::NavigationLoaderInterceptor { class Interceptor final : public content::NavigationLoaderInterceptor {
public: public:
Interceptor() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } Interceptor() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); }
~Interceptor() override { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); } ~Interceptor() override { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); }
private: private:
// NavigationLoaderInterceptor implementation // NavigationLoaderInterceptor implementation
...@@ -24,7 +26,7 @@ class Interceptor final : public content::NavigationLoaderInterceptor { ...@@ -24,7 +26,7 @@ class Interceptor final : public content::NavigationLoaderInterceptor {
content::ResourceContext* resource_context, content::ResourceContext* resource_context,
LoaderCallback callback, LoaderCallback callback,
FallbackCallback fallback_callback) override { FallbackCallback fallback_callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::move(callback).Run({}); std::move(callback).Run({});
} }
...@@ -37,12 +39,17 @@ class Interceptor final : public content::NavigationLoaderInterceptor { ...@@ -37,12 +39,17 @@ class Interceptor final : public content::NavigationLoaderInterceptor {
namespace content { namespace content {
BundledExchangesFactory::BundledExchangesFactory( BundledExchangesFactory::BundledExchangesFactory(
const BundledExchangesSource& bundled_exchanges_source) { const BundledExchangesSource& bundled_exchanges_source)
: reader_(
std::make_unique<BundledExchangesReader>(bundled_exchanges_source)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
reader_->ReadMetadata(base::BindOnce(
&BundledExchangesFactory::OnMetadataReady, weak_factory_.GetWeakPtr()));
} }
// Can be destructed on IO or UI thread. BundledExchangesFactory::~BundledExchangesFactory() {
BundledExchangesFactory::~BundledExchangesFactory() = default; DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
std::unique_ptr<NavigationLoaderInterceptor> std::unique_ptr<NavigationLoaderInterceptor>
BundledExchangesFactory::CreateInterceptor() { BundledExchangesFactory::CreateInterceptor() {
...@@ -58,4 +65,9 @@ void BundledExchangesFactory::CreateURLLoaderFactory( ...@@ -58,4 +65,9 @@ void BundledExchangesFactory::CreateURLLoaderFactory(
NOTREACHED(); NOTREACHED();
} }
void BundledExchangesFactory::OnMetadataReady(
base::Optional<std::string> error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
} // namespace content } // namespace content
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/web_package/bundled_exchanges_source.h" #include "content/browser/web_package/bundled_exchanges_source.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
namespace content { namespace content {
class BundledExchangesReader;
class NavigationLoaderInterceptor; class NavigationLoaderInterceptor;
// A class to provide interfaces to communicate with a BundledExchanges for // A class to provide interfaces to communicate with a BundledExchanges for
...@@ -36,6 +38,14 @@ class BundledExchangesFactory final { ...@@ -36,6 +38,14 @@ class BundledExchangesFactory final {
mojo::Remote<network::mojom::URLLoaderFactory> fallback_factory); mojo::Remote<network::mojom::URLLoaderFactory> fallback_factory);
private: private:
// Methods called on the UI thread.
void OnMetadataReady(base::Optional<std::string> error);
// Following members can be accessed only on the UI thread.
std::unique_ptr<BundledExchangesReader> reader_;
base::WeakPtrFactory<BundledExchangesFactory> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(BundledExchangesFactory); DISALLOW_COPY_AND_ASSIGN(BundledExchangesFactory);
}; };
......
// 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/bundled_exchanges_reader.h"
#include <limits>
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "content/browser/web_package/bundled_exchanges_source.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.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 "services/data_decoder/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace content {
BundledExchangesReader::SharedFile::SharedFile(const base::FilePath& file_path)
: file_path_(file_path) {
base::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](const base::FilePath& file_path) -> std::unique_ptr<base::File> {
return std::make_unique<base::File>(
file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
},
file_path_),
base::BindOnce(&SharedFile::SetFile, base::RetainedRef(this)));
}
void BundledExchangesReader::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* BundledExchangesReader::SharedFile::operator->() {
DCHECK(file_);
return file_.get();
}
BundledExchangesReader::SharedFile::~SharedFile() {
// Move the last reference to |file_| that leads an internal blocking call
// that is not permitted here.
base::PostTaskWithTraits(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce([](std::unique_ptr<base::File> file) {},
std::move(file_)));
}
void BundledExchangesReader::SharedFile::SetFile(
std::unique_ptr<base::File> file) {
file_ = std::move(file);
if (duplicate_callback_.is_null())
return;
base::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](base::File* file) -> base::File { return file->Duplicate(); },
file_.get()),
std::move(duplicate_callback_));
}
class BundledExchangesReader::SharedFileDataSource final
: public mojo::DataPipeProducer::DataSource {
public:
SharedFileDataSource(scoped_refptr<BundledExchangesReader::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<BundledExchangesReader::SharedFile> file_;
MojoResult error_;
const uint64_t offset_;
const uint64_t length_;
DISALLOW_COPY_AND_ASSIGN(SharedFileDataSource);
};
BundledExchangesReader::BundledExchangesReader(
const BundledExchangesSource& source)
: file_(base::MakeRefCounted<SharedFile>(source.file_path)) {}
BundledExchangesReader::~BundledExchangesReader() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void BundledExchangesReader::ReadMetadata(MetadataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!metadata_ready_);
file_->DuplicateFile(
base::BindOnce(&BundledExchangesReader::ReadMetadataInternal,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void BundledExchangesReader::ReadResponse(const GURL& url,
ResponseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_ready_);
auto it = entries_.find(url);
if (it == entries_.end()) {
PostTask(FROM_HERE, base::BindOnce(std::move(callback), nullptr));
return;
}
parser_.ParseResponse(
it->second->response_offset, it->second->response_length,
base::BindOnce(&BundledExchangesReader::OnResponseParsed,
base::Unretained(this), std::move(callback)));
}
void BundledExchangesReader::ReadResponseBody(
data_decoder::mojom::BundleResponsePtr response,
mojo::ScopedDataPipeProducerHandle producer_handle,
BodyCompletionCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_ready_);
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); },
std::move(data_producer), std::move(callback)));
}
bool BundledExchangesReader::HasEntry(const GURL& url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_ready_);
return entries_.contains(url);
}
const GURL& BundledExchangesReader::GetStartURL() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_ready_);
return start_url_;
}
void BundledExchangesReader::SetBundledExchangesParserFactoryForTesting(
std::unique_ptr<data_decoder::mojom::BundledExchangesParserFactory>
factory) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
parser_.SetBundledExchangesParserFactoryForTesting(std::move(factory));
}
void BundledExchangesReader::ReadMetadataInternal(MetadataCallback callback,
base::File file) {
parser_.OpenFile(
ServiceManagerConnection::GetForProcess()
? ServiceManagerConnection::GetForProcess()->GetConnector()
: nullptr,
std::move(file));
parser_.ParseMetadata(
base::BindOnce(&BundledExchangesReader::OnMetadataParsed,
base::Unretained(this), std::move(callback)));
}
void BundledExchangesReader::OnMetadataParsed(
MetadataCallback callback,
data_decoder::mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!metadata_ready_);
metadata_ready_ = true;
if (metadata) {
// TODO(crbug.com/966753): Use the primary (fallback) URL that the new
// BundledExchanges format defines.
if (!metadata->index.empty())
start_url_ = metadata->index[0]->request_url;
for (auto& item : metadata->index)
entries_.insert(std::make_pair(item->request_url, std::move(item)));
}
std::move(callback).Run(error);
}
void BundledExchangesReader::OnResponseParsed(
ResponseCallback callback,
data_decoder::mojom::BundleResponsePtr response,
const base::Optional<std::string>& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(metadata_ready_);
// TODO(crbug.com/966753): Handle |error|.
std::move(callback).Run(std::move(response));
}
} // namespace content
// 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.
#ifndef CONTENT_BROWSER_WEB_PACKAGE_BUNDLED_EXCHANGES_READER_H_
#define CONTENT_BROWSER_WEB_PACKAGE_BUNDLED_EXCHANGES_READER_H_
#include <string>
#include "base/containers/flat_map.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "services/data_decoder/public/cpp/safe_bundled_exchanges_parser.h"
#include "services/data_decoder/public/mojom/bundled_exchanges_parser.mojom.h"
#include "url/gurl.h"
namespace content {
struct BundledExchangesSource;
// A class to handle a BundledExchanges that is specified by |source|.
// It asks the utility process to parse metadata and response structures, and
// provides body data based on parsed information.
// Running on the UI thread.
class CONTENT_EXPORT BundledExchangesReader final {
public:
explicit BundledExchangesReader(const BundledExchangesSource& source);
~BundledExchangesReader();
// Starts parsing, and runs |callback| when meta data gets to be available.
// |error| is set only on failures.
// Other methods below are only available after this |callback| invocation.
using MetadataCallback =
base::OnceCallback<void(base::Optional<std::string> error)>;
void ReadMetadata(MetadataCallback callback);
// Gets data_decoder::mojom::BundleResponsePtr for the given |url| that
// contains response headers and range information for its body.
// Should be called after ReadMetadata finishes.
using ResponseCallback =
base::OnceCallback<void(data_decoder::mojom::BundleResponsePtr)>;
void ReadResponse(const GURL& url, ResponseCallback callback);
// Starts loading response body. |response| should be obtained by
// ReadResponse above beforehand. Body will be written into |producer_handle|.
// After all body data is written, |callback| will be invoked.
using BodyCompletionCallback = base::OnceCallback<void(MojoResult result)>;
void ReadResponseBody(data_decoder::mojom::BundleResponsePtr response,
mojo::ScopedDataPipeProducerHandle producer_handle,
BodyCompletionCallback callback);
// Returns true if the BundledExchangesSource this object was constructed with
// contains an exchange for |url|.
// Should be called after ReadMetadata finishes.
bool HasEntry(const GURL& url) const;
// Returns start page URL.
// Should be called after ReadMetadata finishes.
const GURL& GetStartURL() const;
void SetBundledExchangesParserFactoryForTesting(
std::unique_ptr<data_decoder::mojom::BundledExchangesParserFactory>
factory);
private:
// A simple wrapper class to share a single base::File instance among multiple
// SharedFileDataSource instances.
class SharedFile final : public base::RefCountedThreadSafe<SharedFile> {
public:
explicit SharedFile(const base::FilePath& file_path);
void DuplicateFile(base::OnceCallback<void(base::File)> callback);
base::File* operator->();
private:
friend class base::RefCountedThreadSafe<SharedFile>;
~SharedFile();
void SetFile(std::unique_ptr<base::File> file);
base::FilePath file_path_;
std::unique_ptr<base::File> file_;
base::OnceCallback<void(base::File)> duplicate_callback_;
DISALLOW_COPY_AND_ASSIGN(SharedFile);
};
class SharedFileDataSource;
void ReadMetadataInternal(MetadataCallback callback, base::File file);
void OnMetadataParsed(MetadataCallback callback,
data_decoder::mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error);
void OnResponseParsed(ResponseCallback callback,
data_decoder::mojom::BundleResponsePtr response,
const base::Optional<std::string>& error);
SEQUENCE_CHECKER(sequence_checker_);
data_decoder::SafeBundledExchangesParser parser_;
scoped_refptr<SharedFile> file_;
GURL start_url_;
base::flat_map<GURL, data_decoder::mojom::BundleIndexItemPtr> entries_;
bool metadata_ready_ = false;
base::WeakPtrFactory<BundledExchangesReader> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(BundledExchangesReader);
};
} // namespace content
#endif // CONTENT_BROWSER_WEB_PACKAGE_BUNDLED_EXCHANGES_READER_H_
// 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/bundled_exchanges_reader.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/test/scoped_task_environment.h"
#include "content/browser/web_package/bundled_exchanges_source.h"
#include "mojo/public/c/system/data_pipe.h"
#include "services/data_decoder/public/mojom/bundled_exchanges_parser.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class MockParser final : public data_decoder::mojom::BundledExchangesParser {
public:
MockParser(mojo::PendingReceiver<data_decoder::mojom::BundledExchangesParser>
receiver)
: receiver_(this, std::move(receiver)) {}
~MockParser() override {}
void RunMetadataCallback(data_decoder::mojom::BundleMetadataPtr metadata) {
base::RunLoop().RunUntilIdle();
std::move(metadata_callback_).Run(std::move(metadata), base::nullopt);
}
void RunResponseCallback(data_decoder::mojom::BundleResponsePtr response) {
base::RunLoop().RunUntilIdle();
std::move(response_callback_).Run(std::move(response), base::nullopt);
}
void WaitUntilParseMetadataCalled(base::OnceClosure closure) {
if (metadata_callback_.is_null())
wait_parse_metadata_callback_ = std::move(closure);
else
std::move(closure).Run();
}
private:
// data_decoder::mojom::BundledExchangesParser implementation.
void ParseMetadata(ParseMetadataCallback callback) override {
metadata_callback_ = std::move(callback);
if (!wait_parse_metadata_callback_.is_null())
std::move(wait_parse_metadata_callback_).Run();
}
void ParseResponse(uint64_t response_offset,
uint64_t response_length,
ParseResponseCallback callback) override {
response_callback_ = std::move(callback);
}
mojo::Receiver<data_decoder::mojom::BundledExchangesParser> receiver_;
ParseMetadataCallback metadata_callback_;
ParseResponseCallback response_callback_;
base::OnceClosure wait_parse_metadata_callback_;
};
class MockParserFactory final
: public data_decoder::mojom::BundledExchangesParserFactory {
public:
MockParserFactory() {}
~MockParserFactory() override {}
void WaitUntilParseMetadataCalled(base::OnceClosure closure) {
if (parser_)
parser_->WaitUntilParseMetadataCalled(std::move(closure));
else
wait_parse_metadata_callback_ = std::move(closure);
}
void RunMetadataCallback(data_decoder::mojom::BundleMetadataPtr metadata) {
base::RunLoop run_loop;
WaitUntilParseMetadataCalled(run_loop.QuitClosure());
run_loop.Run();
ASSERT_TRUE(parser_);
parser_->RunMetadataCallback(std::move(metadata));
}
void RunResponseCallback(data_decoder::mojom::BundleResponsePtr response) {
ASSERT_TRUE(parser_);
parser_->RunResponseCallback(std::move(response));
}
private:
// data_decoder::mojom::BundledExchangesParserFactory implementation.
void GetParserForFile(
mojo::PendingReceiver<data_decoder::mojom::BundledExchangesParser>
receiver,
base::File file) override {
parser_ = std::make_unique<MockParser>(std::move(receiver));
if (!wait_parse_metadata_callback_.is_null()) {
parser_->WaitUntilParseMetadataCalled(
std::move(wait_parse_metadata_callback_));
}
}
void GetParserForDataSource(
mojo::PendingReceiver<data_decoder::mojom::BundledExchangesParser>
receiver,
mojo::PendingRemote<data_decoder::mojom::BundleDataSource> data_source)
override {
NOTREACHED();
}
std::unique_ptr<MockParser> parser_;
base::OnceClosure wait_parse_metadata_callback_;
DISALLOW_COPY_AND_ASSIGN(MockParserFactory);
};
class BundledExchangesReaderTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(
CreateTemporaryFileInDir(temp_dir_.GetPath(), &temp_file_path_));
ASSERT_EQ(base::checked_cast<int>(body_.size()),
base::WriteFile(temp_file_path_, body_.data(), body_.size()));
BundledExchangesSource source(temp_file_path_);
reader_ = std::make_unique<BundledExchangesReader>(source);
std::unique_ptr<MockParserFactory> factory =
std::make_unique<MockParserFactory>();
factory_ = factory.get();
reader_->SetBundledExchangesParserFactoryForTesting(std::move(factory));
}
protected:
void ReadMetadata() {
base::RunLoop run_loop;
reader_->ReadMetadata(base::BindOnce(
[](base::Closure quit_closure, base::Optional<std::string> error) {
EXPECT_FALSE(error);
std::move(quit_closure).Run();
},
run_loop.QuitClosure()));
std::vector<data_decoder::mojom::BundleIndexItemPtr> items;
data_decoder::mojom::BundleIndexItemPtr item =
data_decoder::mojom::BundleIndexItem::New();
item->request_url = start_url_;
item->response_offset = 573u;
item->response_length = 765u;
items.push_back(std::move(item));
data_decoder::mojom::BundleMetadataPtr metadata =
data_decoder::mojom::BundleMetadata::New(std::move(items), GURL());
factory_->RunMetadataCallback(std::move(metadata));
run_loop.Run();
}
BundledExchangesReader* GetReader() { return reader_.get(); }
MockParserFactory* GetFactory() { return factory_; }
const GURL& GetStartURL() const { return start_url_; }
const std::string& GetBody() const { return body_; }
private:
base::test::ScopedTaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
base::FilePath temp_file_path_;
std::unique_ptr<BundledExchangesReader> reader_;
MockParserFactory* factory_;
const GURL start_url_ = GURL("https://www.google.com/");
const std::string body_ = std::string("hello new open world.");
};
TEST_F(BundledExchangesReaderTest, ReadMetadata) {
ReadMetadata();
EXPECT_EQ(GetStartURL(), GetReader()->GetStartURL());
EXPECT_TRUE(GetReader()->HasEntry(GetStartURL()));
EXPECT_FALSE(GetReader()->HasEntry(GURL("https://www.google.com/404")));
}
TEST_F(BundledExchangesReaderTest, ReadResponse) {
ReadMetadata();
ASSERT_TRUE(GetReader()->HasEntry(GetStartURL()));
base::RunLoop run_loop;
GetReader()->ReadResponse(
GetStartURL(), base::BindOnce(
[](base::Closure quit_closure,
data_decoder::mojom::BundleResponsePtr response) {
EXPECT_TRUE(response);
if (response) {
EXPECT_EQ(200, response->response_code);
EXPECT_EQ(0xdeadu, response->payload_offset);
EXPECT_EQ(0xbeafu, response->payload_length);
}
std::move(quit_closure).Run();
},
run_loop.QuitClosure()));
data_decoder::mojom::BundleResponsePtr response =
data_decoder::mojom::BundleResponse::New();
response->response_code = 200;
response->payload_offset = 0xdead;
response->payload_length = 0xbeaf;
GetFactory()->RunResponseCallback(std::move(response));
run_loop.Run();
}
TEST_F(BundledExchangesReaderTest, ReadResponseBody) {
ReadMetadata();
data_decoder::mojom::BundleResponsePtr response =
data_decoder::mojom::BundleResponse::New();
constexpr size_t expected_offset = 4;
const size_t expected_length = GetBody().size() - 8;
response->payload_offset = expected_offset;
response->payload_length = expected_length;
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.element_num_bytes = 1;
options.capacity_num_bytes = response->payload_length + 1;
ASSERT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&options, &producer, &consumer));
base::RunLoop run_loop;
MojoResult callback_result;
GetReader()->ReadResponseBody(
std::move(response), std::move(producer),
base::BindOnce(
[](base::Closure quit_closure, MojoResult* callback_result,
MojoResult result) {
*callback_result = result;
std::move(quit_closure).Run();
},
run_loop.QuitClosure(), &callback_result));
run_loop.Run();
ASSERT_EQ(MOJO_RESULT_OK, callback_result);
std::vector<char> buffer(expected_length);
uint32_t bytes_read = buffer.size();
MojoResult read_result =
consumer->ReadData(buffer.data(), &bytes_read, /*flags=*/0);
ASSERT_EQ(MOJO_RESULT_OK, read_result);
ASSERT_EQ(buffer.size(), bytes_read);
EXPECT_EQ(GetBody().substr(expected_offset, expected_length),
std::string(buffer.data(), bytes_read));
}
} // namespace
} // namespace content
...@@ -105,6 +105,7 @@ const service_manager::Manifest& GetContentBrowserManifest() { ...@@ -105,6 +105,7 @@ const service_manager::Manifest& GetContentBrowserManifest() {
"discardable_memory.mojom.DiscardableSharedMemoryManager", "discardable_memory.mojom.DiscardableSharedMemoryManager",
"media.mojom.AndroidOverlayProvider", "media.mojom.AndroidOverlayProvider",
}) })
.RequireCapability("data_decoder", "bundled_exchanges_parser_factory")
.RequireCapability("data_decoder", "image_decoder") .RequireCapability("data_decoder", "image_decoder")
.RequireCapability("data_decoder", "json_parser") .RequireCapability("data_decoder", "json_parser")
.RequireCapability("data_decoder", "xml_parser") .RequireCapability("data_decoder", "xml_parser")
......
...@@ -1815,6 +1815,7 @@ test("content_unittests") { ...@@ -1815,6 +1815,7 @@ test("content_unittests") {
"../browser/web_contents/web_contents_view_mac_unittest.mm", "../browser/web_contents/web_contents_view_mac_unittest.mm",
"../browser/web_contents/web_drag_dest_mac_unittest.mm", "../browser/web_contents/web_drag_dest_mac_unittest.mm",
"../browser/web_contents/web_drag_source_mac_unittest.mm", "../browser/web_contents/web_drag_source_mac_unittest.mm",
"../browser/web_package/bundled_exchanges_reader_unittest.cc",
"../browser/web_package/signed_exchange_cert_fetcher_unittest.cc", "../browser/web_package/signed_exchange_cert_fetcher_unittest.cc",
"../browser/web_package/signed_exchange_certificate_chain_unittest.cc", "../browser/web_package/signed_exchange_certificate_chain_unittest.cc",
"../browser/web_package/signed_exchange_envelope_unittest.cc", "../browser/web_package/signed_exchange_envelope_unittest.cc",
...@@ -2043,6 +2044,7 @@ test("content_unittests") { ...@@ -2043,6 +2044,7 @@ test("content_unittests") {
"//printing", "//printing",
"//services/audio/public/cpp:test_support", "//services/audio/public/cpp:test_support",
"//services/audio/public/mojom", "//services/audio/public/mojom",
"//services/data_decoder/public/mojom",
"//services/device/public/cpp:test_support", "//services/device/public/cpp:test_support",
"//services/device/public/cpp/generic_sensor", "//services/device/public/cpp/generic_sensor",
"//services/device/public/mojom", "//services/device/public/mojom",
......
...@@ -55,6 +55,7 @@ source_set("tests") { ...@@ -55,6 +55,7 @@ source_set("tests") {
"bundled_exchanges_parser_unittest.cc", "bundled_exchanges_parser_unittest.cc",
"image_decoder_impl_unittest.cc", "image_decoder_impl_unittest.cc",
"public/cpp/json_sanitizer_unittest.cc", "public/cpp/json_sanitizer_unittest.cc",
"public/cpp/safe_bundled_exchanges_parser_unittest.cc",
"public/cpp/safe_xml_parser_unittest.cc", "public/cpp/safe_xml_parser_unittest.cc",
"public/cpp/testing_json_parser_unittest.cc", "public/cpp/testing_json_parser_unittest.cc",
"xml_parser_unittest.cc", "xml_parser_unittest.cc",
......
...@@ -25,6 +25,8 @@ source_set("cpp") { ...@@ -25,6 +25,8 @@ source_set("cpp") {
"decode_image.cc", "decode_image.cc",
"decode_image.h", "decode_image.h",
"json_sanitizer.h", "json_sanitizer.h",
"safe_bundled_exchanges_parser.cc",
"safe_bundled_exchanges_parser.h",
"safe_json_parser_impl.cc", "safe_json_parser_impl.cc",
"safe_json_parser_impl.h", "safe_json_parser_impl.h",
"safe_xml_parser.cc", "safe_xml_parser.cc",
......
// 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 "services/data_decoder/public/cpp/safe_bundled_exchanges_parser.h"
#include "base/bind.h"
#include "services/data_decoder/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace data_decoder {
namespace {
constexpr char kConnectionError[] =
"Cannot connect to the remote parser service";
} // namespace
SafeBundledExchangesParser::SafeBundledExchangesParser() = default;
SafeBundledExchangesParser::~SafeBundledExchangesParser() = default;
void SafeBundledExchangesParser::OpenFile(service_manager::Connector* connector,
base::File file) {
DCHECK(disconnected_);
if (factory_for_testing_) {
factory_for_testing_->GetParserForFile(parser_.BindNewPipeAndPassReceiver(),
std::move(file));
} else {
mojo::Remote<mojom::BundledExchangesParserFactory> factory;
connector->Connect(mojom::kServiceName,
factory.BindNewPipeAndPassReceiver());
factory->GetParserForFile(parser_.BindNewPipeAndPassReceiver(),
std::move(file));
}
parser_.set_disconnect_handler(base::BindOnce(
&SafeBundledExchangesParser::OnDisconnect, base::Unretained(this)));
disconnected_ = false;
}
void SafeBundledExchangesParser::OpenDataSource(
service_manager::Connector* connector,
mojo::PendingRemote<mojom::BundleDataSource> data_source) {
DCHECK(disconnected_);
if (factory_for_testing_) {
factory_for_testing_->GetParserForDataSource(
parser_.BindNewPipeAndPassReceiver(), std::move(data_source));
} else {
mojo::Remote<mojom::BundledExchangesParserFactory> factory;
connector->Connect(mojom::kServiceName,
factory.BindNewPipeAndPassReceiver());
factory->GetParserForDataSource(parser_.BindNewPipeAndPassReceiver(),
std::move(data_source));
}
parser_.set_disconnect_handler(base::BindOnce(
&SafeBundledExchangesParser::OnDisconnect, base::Unretained(this)));
disconnected_ = false;
}
void SafeBundledExchangesParser::ParseMetadata(
mojom::BundledExchangesParser::ParseMetadataCallback callback) {
// This method is designed to be called once. So, allowing only once
// simultaneous request is fine enough.
if (disconnected_ || !metadata_callback_.is_null()) {
std::move(callback).Run(nullptr, kConnectionError);
return;
}
metadata_callback_ = std::move(callback);
parser_->ParseMetadata(base::BindOnce(
&SafeBundledExchangesParser::OnMetadataParsed, base::Unretained(this)));
}
void SafeBundledExchangesParser::ParseResponse(
uint64_t response_offset,
uint64_t response_length,
mojom::BundledExchangesParser::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 (disconnected_ ||
response_callbacks_.contains(response_callback_next_id_)) {
std::move(callback).Run(nullptr, kConnectionError);
return;
}
size_t callback_id = response_callback_next_id_++;
response_callbacks_[callback_id] = std::move(callback);
parser_->ParseResponse(
response_offset, response_length,
base::BindOnce(&SafeBundledExchangesParser::OnResponseParsed,
base::Unretained(this), callback_id));
}
void SafeBundledExchangesParser::OnDisconnect() {
disconnected_ = true;
if (!metadata_callback_.is_null())
std::move(metadata_callback_).Run(nullptr, kConnectionError);
for (auto& callback : response_callbacks_)
std::move(callback.second).Run(nullptr, kConnectionError);
response_callbacks_.clear();
}
void SafeBundledExchangesParser::OnMetadataParsed(
mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error_message) {
DCHECK(!metadata_callback_.is_null());
std::move(metadata_callback_)
.Run(std::move(metadata), std::move(error_message));
}
void SafeBundledExchangesParser::OnResponseParsed(
size_t callback_id,
mojom::BundleResponsePtr response,
const base::Optional<std::string>& error_message) {
auto it = response_callbacks_.find(callback_id);
DCHECK(it != response_callbacks_.end());
auto callback = std::move(it->second);
response_callbacks_.erase(it);
std::move(callback).Run(std::move(response), std::move(error_message));
}
void SafeBundledExchangesParser::SetBundledExchangesParserFactoryForTesting(
std::unique_ptr<mojom::BundledExchangesParserFactory> factory) {
factory_for_testing_ = std::move(factory);
}
} // namespace data_decoder
...@@ -5,28 +5,78 @@ ...@@ -5,28 +5,78 @@
#ifndef SERVICES_DATA_DECODER_PUBLIC_CPP_SAFE_BUNDLED_EXCHANGES_PARSER_H_ #ifndef SERVICES_DATA_DECODER_PUBLIC_CPP_SAFE_BUNDLED_EXCHANGES_PARSER_H_
#define SERVICES_DATA_DECODER_PUBLIC_CPP_SAFE_BUNDLED_EXCHANGES_PARSER_H_ #define SERVICES_DATA_DECODER_PUBLIC_CPP_SAFE_BUNDLED_EXCHANGES_PARSER_H_
#include <string>
#include "base/containers/flat_map.h"
#include "base/files/file.h"
#include "base/optional.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/data_decoder/public/mojom/bundled_exchanges_parser.mojom.h" #include "services/data_decoder/public/mojom/bundled_exchanges_parser.mojom.h"
namespace service_manager { namespace service_manager {
class Connector; class Connector;
} } // namespace service_manager
namespace data_decoder { namespace data_decoder {
// Parses bundle's metadata from |data_source| via the data_decoder service. // A class to wrap remote mojom::BundledExchangesParserFactory and
void ParseBundledExchangesMetadata( // mojom::BundledExchangesParser service.
service_manager::Connector* connector, class SafeBundledExchangesParser {
mojo::PendingRemote<mojom::BundleDataSource> data_source, public:
mojom::BundledExchangesParser::ParseMetadataCallback callback); SafeBundledExchangesParser();
// Remaining callbacks on flight will be dropped.
// Parses a response from |data_source| that starts at |response_offset| and ~SafeBundledExchangesParser();
// has length |response_length|, via the data_decoder service.
void ParseBundledExchangesResponse( // Binds |this| instance to the given |file| for subsequent parse calls.
service_manager::Connector* connector, // |connector| can be null if SetBundledExchangesParserForTesting() was
mojo::PendingRemote<mojom::BundleDataSource> data_source, // called.
uint64_t response_offset, void OpenFile(service_manager::Connector* connector, base::File file);
uint64_t response_length,
mojom::BundledExchangesParser::ParseResponseCallback callback); // Binds |this| instance to the given |data_source| for subsequent parse
// calls. |connector| can be null if SetBundledExchangesParserForTesting()
// was called.
void OpenDataSource(service_manager::Connector* connector,
mojo::PendingRemote<mojom::BundleDataSource> data_source);
// Parses metadata. See mojom::BundledExchangesParser::ParseMetadata for
// details. This method fails when it's called before the previous call
// finishes.
void ParseMetadata(
mojom::BundledExchangesParser::ParseMetadataCallback callback);
// Parses response. See mojom::BundledExchangesParser::ParseResponse for
// details.
void ParseResponse(
uint64_t response_offset,
uint64_t response_length,
mojom::BundledExchangesParser::ParseResponseCallback callback);
// Sets alternative BundledExchangesParserFactory that will be used to create
// BundledExchangesParser for testing purpose.
void SetBundledExchangesParserFactoryForTesting(
std::unique_ptr<mojom::BundledExchangesParserFactory> factory);
private:
void OnDisconnect();
void OnMetadataParsed(mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error_message);
void OnResponseParsed(size_t callback_id,
mojom::BundleResponsePtr response,
const base::Optional<std::string>& error_message);
mojo::Remote<mojom::BundledExchangesParser> parser_;
mojom::BundledExchangesParser::ParseMetadataCallback metadata_callback_;
base::flat_map<size_t, mojom::BundledExchangesParser::ParseResponseCallback>
response_callbacks_;
size_t response_callback_next_id_ = 0;
bool disconnected_ = true;
std::unique_ptr<mojom::BundledExchangesParserFactory> factory_for_testing_ =
nullptr;
DISALLOW_COPY_AND_ASSIGN(SafeBundledExchangesParser);
};
} // namespace data_decoder } // namespace data_decoder
......
// 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 "services/data_decoder/public/cpp/safe_bundled_exchanges_parser.h"
#include <memory>
#include <string>
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace data_decoder {
namespace {
constexpr char kConnectionError[] =
"Cannot connect to the remote parser service";
base::File OpenTestFile(const base::FilePath& path) {
base::FilePath test_data_dir;
base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir);
test_data_dir = test_data_dir.Append(base::FilePath(
FILE_PATH_LITERAL("services/test/data/bundled_exchanges")));
test_data_dir = test_data_dir.Append(path);
return base::File(test_data_dir,
base::File::FLAG_OPEN | base::File::FLAG_READ);
}
class MockFactory final : public mojom::BundledExchangesParserFactory {
public:
class MockParser final : public mojom::BundledExchangesParser {
public:
MockParser(mojo::PendingReceiver<mojom::BundledExchangesParser> receiver)
: receiver_(this, std::move(receiver)) {}
bool IsParseMetadataCalled() { return !metadata_callback_.is_null(); }
bool IsParseResponseCalled() { return !response_callback_.is_null(); }
void Disconnect() { receiver_.reset(); }
private:
// mojom::BundledExchangesParser implementation.
void ParseMetadata(ParseMetadataCallback callback) override {
metadata_callback_ = std::move(callback);
}
void ParseResponse(uint64_t response_offset,
uint64_t response_length,
ParseResponseCallback callback) override {
response_callback_ = std::move(callback);
}
ParseMetadataCallback metadata_callback_;
ParseResponseCallback response_callback_;
mojo::Receiver<mojom::BundledExchangesParser> receiver_;
DISALLOW_COPY_AND_ASSIGN(MockParser);
};
MockFactory() {}
MockParser* GetCreatedParser() { return parser_.get(); }
void DeleteParser() { parser_.reset(); }
private:
// mojom::BundledExchangesParserFactory implementation.
void GetParserForFile(
mojo::PendingReceiver<mojom::BundledExchangesParser> receiver,
base::File file) override {
parser_ = std::make_unique<MockParser>(std::move(receiver));
}
void GetParserForDataSource(
mojo::PendingReceiver<mojom::BundledExchangesParser> receiver,
mojo::PendingRemote<mojom::BundleDataSource> data_source) override {
parser_ = std::make_unique<MockParser>(std::move(receiver));
}
std::unique_ptr<MockParser> parser_;
DISALLOW_COPY_AND_ASSIGN(MockFactory);
};
class MockDataSource final : public mojom::BundleDataSource {
public:
MockDataSource(mojo::PendingReceiver<mojom::BundleDataSource> receiver)
: receiver_(this, std::move(receiver)) {}
private:
// Implements mojom::BundledDataSource.
void GetSize(GetSizeCallback callback) override {}
void Read(uint64_t offset, uint64_t length, ReadCallback callback) override {}
mojo::Receiver<mojom::BundleDataSource> receiver_;
DISALLOW_COPY_AND_ASSIGN(MockDataSource);
};
} // namespace
class SafeBundledExchangesParserTest : public testing::Test {
public:
service_manager::Connector* GetConnector() { return service_.connector(); }
private:
base::test::ScopedTaskEnvironment task_environment_;
TestDataDecoderService service_;
};
TEST_F(SafeBundledExchangesParserTest, ParseGoldenFile) {
SafeBundledExchangesParser parser;
base::File test_file =
OpenTestFile(base::FilePath(FILE_PATH_LITERAL("hello.wbn")));
parser.OpenFile(GetConnector(), std::move(test_file));
mojom::BundleMetadataPtr metadata_result;
{
base::RunLoop run_loop;
parser.ParseMetadata(base::BindOnce(
[](base::Closure quit_closure,
mojom::BundleMetadataPtr* metadata_result,
mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error) {
EXPECT_TRUE(metadata);
EXPECT_FALSE(error);
if (metadata)
*metadata_result = std::move(metadata);
std::move(quit_closure).Run();
},
run_loop.QuitClosure(), &metadata_result));
run_loop.Run();
}
ASSERT_TRUE(metadata_result);
ASSERT_EQ(metadata_result->index.size(), 4u);
const auto& index = metadata_result->index;
std::vector<mojom::BundleResponsePtr> responses;
for (auto& entry : index) {
base::RunLoop run_loop;
parser.ParseResponse(
entry->response_offset, entry->response_length,
base::BindOnce(
[](base::Closure quit_closure,
std::vector<mojom::BundleResponsePtr>* responses,
mojom::BundleResponsePtr response,
const base::Optional<std::string>& error) {
EXPECT_TRUE(response);
EXPECT_FALSE(error);
if (response)
responses->push_back(std::move(response));
std::move(quit_closure).Run();
},
run_loop.QuitClosure(), &responses));
run_loop.Run();
}
EXPECT_EQ(index[0]->request_url, "https://test.example.org/");
EXPECT_EQ(index[0]->request_method, "GET");
EXPECT_EQ(index[0]->request_headers.size(), 0u);
EXPECT_EQ(responses[0]->response_code, 200);
EXPECT_EQ(responses[0]->response_headers["content-type"],
"text/html; charset=utf-8");
EXPECT_EQ(index[1]->request_url, "https://test.example.org/index.html");
EXPECT_EQ(index[2]->request_url,
"https://test.example.org/manifest.webmanifest");
EXPECT_EQ(index[3]->request_url, "https://test.example.org/script.js");
}
TEST_F(SafeBundledExchangesParserTest, CallWithoutOpen) {
SafeBundledExchangesParser parser;
bool metadata_parsed = false;
parser.ParseMetadata(base::BindOnce(
[](bool* metadata_parsed, mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error) {
EXPECT_FALSE(metadata);
EXPECT_TRUE(error);
if (error)
EXPECT_EQ(kConnectionError, *error);
*metadata_parsed = true;
},
&metadata_parsed));
EXPECT_TRUE(metadata_parsed);
bool response_parsed = false;
parser.ParseResponse(
0u, 0u,
base::BindOnce(
[](bool* response_parsed, mojom::BundleResponsePtr response,
const base::Optional<std::string>& error) {
EXPECT_FALSE(response);
EXPECT_TRUE(error);
if (error)
EXPECT_EQ(kConnectionError, *error);
*response_parsed = true;
},
&response_parsed));
EXPECT_TRUE(response_parsed);
}
TEST_F(SafeBundledExchangesParserTest, UseMockFactory) {
SafeBundledExchangesParser parser;
std::unique_ptr<MockFactory> factory = std::make_unique<MockFactory>();
MockFactory* raw_factory = factory.get();
parser.SetBundledExchangesParserFactoryForTesting(std::move(factory));
EXPECT_FALSE(raw_factory->GetCreatedParser());
parser.OpenFile(GetConnector(), base::File());
ASSERT_TRUE(raw_factory->GetCreatedParser());
EXPECT_FALSE(raw_factory->GetCreatedParser()->IsParseMetadataCalled());
EXPECT_FALSE(raw_factory->GetCreatedParser()->IsParseResponseCalled());
parser.ParseMetadata(base::DoNothing());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(raw_factory->GetCreatedParser()->IsParseMetadataCalled());
EXPECT_FALSE(raw_factory->GetCreatedParser()->IsParseResponseCalled());
parser.ParseResponse(0u, 0u, base::DoNothing());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(raw_factory->GetCreatedParser()->IsParseMetadataCalled());
EXPECT_TRUE(raw_factory->GetCreatedParser()->IsParseResponseCalled());
}
TEST_F(SafeBundledExchangesParserTest, ConnectionError) {
SafeBundledExchangesParser parser;
std::unique_ptr<MockFactory> factory = std::make_unique<MockFactory>();
MockFactory* raw_factory = factory.get();
parser.SetBundledExchangesParserFactoryForTesting(std::move(factory));
mojo::PendingRemote<mojom::BundleDataSource> remote_data_source;
auto data_source = std::make_unique<MockDataSource>(
remote_data_source.InitWithNewPipeAndPassReceiver());
parser.OpenDataSource(GetConnector(), std::move(remote_data_source));
ASSERT_TRUE(raw_factory->GetCreatedParser());
base::RunLoop run_loop;
bool parsed = false;
parser.ParseMetadata(base::BindOnce(
[](base::Closure quit_closure, bool* parsed,
mojom::BundleMetadataPtr metadata,
const base::Optional<std::string>& error) {
EXPECT_FALSE(metadata);
EXPECT_TRUE(error);
if (error)
EXPECT_EQ(kConnectionError, *error);
*parsed = true;
std::move(quit_closure).Run();
},
run_loop.QuitClosure(), &parsed));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(parsed);
EXPECT_TRUE(raw_factory->GetCreatedParser()->IsParseMetadataCalled());
raw_factory->GetCreatedParser()->Disconnect();
run_loop.Run();
ASSERT_TRUE(parsed);
// Passed callback is called by SafeBundledExchangesParser on the interface
// disconnection, but remote parser still holds the proxy callback.
EXPECT_TRUE(raw_factory->GetCreatedParser()->IsParseMetadataCalled());
}
} // namespace data_decoder
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment