blob: 3e9315f88625549568b743a632aaf8ca6dede195 [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/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 "base/task/post_task.h"
#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:
explicit ValueParseRequest(DataDecoder::ResultCallback<V> callback)
: callback_(std::move(callback)) {}
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())
return;
DataDecoder::ResultOrError<V> result;
if (value)
result.value = std::move(value);
else
result.error = 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 (callback()) {
std::move(callback())
.Run(DataDecoder::ResultOrError<V>::Error(
"Data Decoder terminated unexpectedly"));
}
}
mojo::Remote<T> remote_;
DataDecoder::ResultCallback<V> callback_;
};
#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
} // namespace
DataDecoder::DataDecoder() : idle_timeout_(kServiceProcessIdleTimeoutDefault) {}
DataDecoder::DataDecoder(base::TimeDelta idle_timeout)
: idle_timeout_(idle_timeout) {}
DataDecoder::~DataDecoder() = default;
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(IS_ANDROID)
// For Android, we use the in-process sanitizer and then parse with a simple
// JSONReader.
JsonSanitizer::Sanitize(
json, base::BindOnce(
[](ValueParseCallback callback, JsonSanitizer::Result result) {
if (!result.value) {
std::move(callback).Run(ValueOrError::Error(*result.error));
return;
}
base::JSONReader::ValueWithError value_with_error =
base::JSONReader::ReadAndReturnValueWithError(
*result.value, base::JSON_PARSE_RFC);
if (!value_with_error.value) {
std::move(callback).Run(
ValueOrError::Error(value_with_error.error_message));
return;
}
std::move(callback).Run(
ValueOrError::Value(std::move(*value_with_error.value)));
},
std::move(callback)));
#else
auto request =
base::MakeRefCounted<ValueParseRequest<mojom::JsonParser, base::Value>>(
std::move(callback));
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,
ValueParseCallback callback) {
auto request =
base::MakeRefCounted<ValueParseRequest<mojom::XmlParser, base::Value>>(
std::move(callback));
GetService()->BindXmlParser(request->BindRemote());
request->remote()->Parse(
xml,
base::BindOnce(&ValueParseRequest<mojom::XmlParser,
base::Value>::OnServiceValueOrError,
request));
}
// static
void DataDecoder::ParseXmlIsolated(const std::string& xml,
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, 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::GzipCompress(base::span<const uint8_t> data,
GzipperCallback callback) {
auto request = base::MakeRefCounted<
ValueParseRequest<mojom::Gzipper, mojo_base::BigBuffer>>(
std::move(callback));
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));
GetService()->BindGzipper(request->BindRemote());
request->remote()->Uncompress(
data,
base::BindOnce(&ValueParseRequest<mojom::Gzipper,
mojo_base::BigBuffer>::OnServiceValue,
request));
}
} // namespace data_decoder