blob: e5f42e0b4095b76c9af809df32c55a912f99ac5f [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 "services/data_decoder/public/cpp/data_decoder.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/parsing_buildflags.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "services/data_decoder/public/mojom/gzipper.mojom.h"
#include "services/data_decoder/public/mojom/json_parser.mojom.h"
#include "services/data_decoder/public/mojom/xml_parser.mojom.h"
#if BUILDFLAG(IS_ANDROID)
#include "services/data_decoder/public/cpp/json_sanitizer.h"
#endif
#if BUILDFLAG(IS_IOS)
#include "services/data_decoder/data_decoder_service.h" // nogncheck
#endif
namespace data_decoder {
namespace {
// The default amount of idle time to tolerate on a DataDecoder instance. If the
// instance is unused for this period of time, the underlying service process
// (if any) may be killed and only restarted once needed again.
// On platforms (like iOS) or environments (like some unit tests) where
// out-of-process services are not used, this has no effect.
constexpr base::TimeDelta kServiceProcessIdleTimeoutDefault{base::Seconds(5)};
// Encapsulates an in-process data decoder parsing request. This provides shared
// ownership of the caller's callback so that it may be invoked exactly once by
// *either* the successful response handler *or* the parsers's disconnection
// handler. This also owns a Remote<T> which is kept alive for the duration of
// the request.
template <typename T, typename V>
class ValueParseRequest : public base::RefCounted<ValueParseRequest<T, V>> {
public:
ValueParseRequest(DataDecoder::ResultCallback<V> callback,
scoped_refptr<DataDecoder::CancellationFlag> is_cancelled)
: callback_(std::move(callback)), is_cancelled_(is_cancelled) {}
ValueParseRequest(const ValueParseRequest&) = delete;
ValueParseRequest& operator=(const ValueParseRequest&) = delete;
mojo::Remote<T>& remote() { return remote_; }
DataDecoder::ResultCallback<V>& callback() { return callback_; }
// Creates a pipe and binds it to the remote(), and sets up the
// disconnect handler to invoke callback() with an error.
mojo::PendingReceiver<T> BindRemote() {
auto receiver = remote_.BindNewPipeAndPassReceiver();
remote_.set_disconnect_handler(
base::BindOnce(&ValueParseRequest::OnRemoteDisconnected, this));
return receiver;
}
void OnServiceValue(absl::optional<V> value) {
OnServiceValueOrError(std::move(value), absl::nullopt);
}
// Handles a successful parse from the service.
void OnServiceValueOrError(absl::optional<V> value,
const absl::optional<std::string>& error) {
if (!callback() || is_cancelled_->data)
return;
base::expected<V, std::string> result;
if (value)
result = std::move(*value);
else
result = base::unexpected(error.value_or("unknown error"));
// Copy the callback onto the stack before resetting the Remote, as that may
// delete |this|.
auto local_callback = std::move(callback());
// Reset the |remote_| since we aren't using it again and we don't want it
// to trip the disconnect handler. May delete |this|.
remote_.reset();
// We run the callback after reset just in case it does anything funky like
// spin a nested RunLoop.
std::move(local_callback).Run(std::move(result));
}
private:
friend class base::RefCounted<ValueParseRequest>;
~ValueParseRequest() = default;
void OnRemoteDisconnected() {
if (is_cancelled_->data)
return;
if (callback()) {
std::move(callback())
.Run(base::unexpected("Data Decoder terminated unexpectedly"));
}
}
mojo::Remote<T> remote_;
DataDecoder::ResultCallback<V> callback_;
scoped_refptr<DataDecoder::CancellationFlag> is_cancelled_;
};
#if BUILDFLAG(IS_IOS)
void BindInProcessService(
mojo::PendingReceiver<mojom::DataDecoderService> receiver) {
static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>>
task_runner{base::ThreadPool::CreateSequencedTaskRunner({})};
if (!(*task_runner)->RunsTasksInCurrentSequence()) {
(*task_runner)
->PostTask(FROM_HERE,
base::BindOnce(&BindInProcessService, std::move(receiver)));
return;
}
static base::NoDestructor<DataDecoderService> service;
service->BindReceiver(std::move(receiver));
}
#endif
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_PARSER)
void ParsingComplete(scoped_refptr<DataDecoder::CancellationFlag> is_cancelled,
DataDecoder::ValueParseCallback callback,
base::JSONReader::Result value_with_error) {
if (is_cancelled->data)
return;
if (!value_with_error.has_value()) {
std::move(callback).Run(base::unexpected(value_with_error.error().message));
} else {
std::move(callback).Run(std::move(*value_with_error));
}
}
#endif
} // namespace
DataDecoder::DataDecoder() : DataDecoder(kServiceProcessIdleTimeoutDefault) {}
DataDecoder::DataDecoder(base::TimeDelta idle_timeout)
: idle_timeout_(idle_timeout),
cancel_requests_(new CancellationFlag(false)) {}
DataDecoder::~DataDecoder() {
cancel_requests_->data = true;
}
mojom::DataDecoderService* DataDecoder::GetService() {
// Lazily start an instance of the service if possible and necessary.
if (!service_) {
auto* provider = ServiceProvider::Get();
if (provider) {
provider->BindDataDecoderService(service_.BindNewPipeAndPassReceiver());
} else {
#if BUILDFLAG(IS_IOS)
BindInProcessService(service_.BindNewPipeAndPassReceiver());
#else
LOG(FATAL) << "data_decoder::ServiceProvider::Set() must be called "
<< "before any instances of DataDecoder can be used.";
return nullptr;
#endif
}
service_.reset_on_disconnect();
service_.reset_on_idle_timeout(idle_timeout_);
}
return service_.get();
}
void DataDecoder::ParseJson(const std::string& json,
ValueParseCallback callback) {
#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
// Parses JSON directly in the calling process using the memory-safe
// Rust parser.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(
[](const std::string& json) {
return base::JSONReader::ReadAndReturnValueWithError(
json, base::JSON_PARSE_RFC);
},
json),
base::BindOnce(&ParsingComplete, cancel_requests_, std::move(callback)));
#elif BUILDFLAG(IS_ANDROID)
// For Android, if the full Rust parser is not available, we use the
// in-process sanitizer and then parse in-process.
JsonSanitizer::Sanitize(
json, base::BindOnce(
[](ValueParseCallback callback,
scoped_refptr<CancellationFlag> is_cancelled,
JsonSanitizer::Result result) {
if (is_cancelled->data)
return;
if (!result.value) {
std::move(callback).Run(base::unexpected(*result.error));
return;
}
auto value_with_error =
base::JSONReader::ReadAndReturnValueWithError(
*result.value, base::JSON_PARSE_RFC);
ParsingComplete(is_cancelled, std::move(callback),
std::move(value_with_error));
},
std::move(callback), cancel_requests_));
#else
// Parse JSON out-of-process.
auto request =
base::MakeRefCounted<ValueParseRequest<mojom::JsonParser, base::Value>>(
std::move(callback), cancel_requests_);
GetService()->BindJsonParser(request->BindRemote());
request->remote()->Parse(
json, base::JSON_PARSE_RFC,
base::BindOnce(&ValueParseRequest<mojom::JsonParser,
base::Value>::OnServiceValueOrError,
request));
#endif
}
// static
void DataDecoder::ParseJsonIsolated(const std::string& json,
ValueParseCallback callback) {
auto decoder = std::make_unique<DataDecoder>();
auto* raw_decoder = decoder.get();
// We bind the DataDecoder's ownership into the result callback to ensure that
// it stays alive until the operation is complete.
raw_decoder->ParseJson(
json, base::BindOnce(
[](std::unique_ptr<DataDecoder>, ValueParseCallback callback,
ValueOrError result) {
std::move(callback).Run(std::move(result));
},
std::move(decoder), std::move(callback)));
}
void DataDecoder::ParseXml(
const std::string& xml,
mojom::XmlParser::WhitespaceBehavior whitespace_behavior,
ValueParseCallback callback) {
auto request =
base::MakeRefCounted<ValueParseRequest<mojom::XmlParser, base::Value>>(
std::move(callback), cancel_requests_);
GetService()->BindXmlParser(request->BindRemote());
request->remote()->Parse(
xml, whitespace_behavior,
base::BindOnce(&ValueParseRequest<mojom::XmlParser,
base::Value>::OnServiceValueOrError,
request));
}
// static
void DataDecoder::ParseXmlIsolated(
const std::string& xml,
mojom::XmlParser::WhitespaceBehavior whitespace_behavior,
ValueParseCallback callback) {
auto decoder = std::make_unique<DataDecoder>();
auto* raw_decoder = decoder.get();
// We bind the DataDecoder's ownership into the result callback to ensure that
// it stays alive until the operation is complete.
raw_decoder->ParseXml(
xml, whitespace_behavior,
base::BindOnce(
[](std::unique_ptr<DataDecoder>, ValueParseCallback callback,
ValueOrError result) {
std::move(callback).Run(std::move(result));
},
std::move(decoder), std::move(callback)));
}
void DataDecoder::Deflate(base::span<const uint8_t> data,
GzipperCallback callback) {
auto request = base::MakeRefCounted<
ValueParseRequest<mojom::Gzipper, mojo_base::BigBuffer>>(
std::move(callback), cancel_requests_);
GetService()->BindGzipper(request->BindRemote());
request->remote()->Deflate(
data,
base::BindOnce(&ValueParseRequest<mojom::Gzipper,
mojo_base::BigBuffer>::OnServiceValue,
request));
}
void DataDecoder::Inflate(base::span<const uint8_t> data,
uint64_t max_uncompressed_size,
GzipperCallback callback) {
auto request = base::MakeRefCounted<
ValueParseRequest<mojom::Gzipper, mojo_base::BigBuffer>>(
std::move(callback), cancel_requests_);
GetService()->BindGzipper(request->BindRemote());
request->remote()->Inflate(
data, max_uncompressed_size,
base::BindOnce(&ValueParseRequest<mojom::Gzipper,
mojo_base::BigBuffer>::OnServiceValue,
request));
}
void DataDecoder::GzipCompress(base::span<const uint8_t> data,
GzipperCallback callback) {
auto request = base::MakeRefCounted<
ValueParseRequest<mojom::Gzipper, mojo_base::BigBuffer>>(
std::move(callback), cancel_requests_);
GetService()->BindGzipper(request->BindRemote());
request->remote()->Compress(
data,
base::BindOnce(&ValueParseRequest<mojom::Gzipper,
mojo_base::BigBuffer>::OnServiceValue,
request));
}
void DataDecoder::GzipUncompress(base::span<const uint8_t> data,
GzipperCallback callback) {
auto request = base::MakeRefCounted<
ValueParseRequest<mojom::Gzipper, mojo_base::BigBuffer>>(
std::move(callback), cancel_requests_);
GetService()->BindGzipper(request->BindRemote());
request->remote()->Uncompress(
data,
base::BindOnce(&ValueParseRequest<mojom::Gzipper,
mojo_base::BigBuffer>::OnServiceValue,
request));
}
} // namespace data_decoder