Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qjsonrpcprotocol_p_p.h"
#include <QtCore/qjsonarray.h>
#include <QtCore/qstring.h>
#include <QtCore/qjsonobject.h>
#include <functional>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static QJsonObject createResponse(const QJsonValue &id, const QJsonRpcProtocol::Response &response)
{
QJsonObject object;
object.insert(u"jsonrpc", u"2.0"_s);
object.insert(u"id", id);
if (response.errorCode.isDouble()) {
QJsonObject error;
error.insert(u"code", response.errorCode);
error.insert(u"message", response.errorMessage);
if (!response.data.isUndefined())
error.insert(u"data", response.data);
object.insert(u"error", error);
} else {
object.insert(u"result", response.data);
}
return object;
}
static QJsonRpcProtocol::Response
createPredefinedError(QJsonRpcProtocol::ErrorCode code,
const QJsonValue &id = QJsonValue::Undefined)
{
QJsonRpcProtocol::Response response;
response.errorCode = static_cast<double>(code);
switch (code) {
case QJsonRpcProtocol::ErrorCode::ParseError:
response.errorMessage = u"Parse error"_s;
break;
case QJsonRpcProtocol::ErrorCode::InvalidRequest:
response.errorMessage = u"Invalid Request"_s;
break;
case QJsonRpcProtocol::ErrorCode::MethodNotFound:
response.errorMessage = u"Method not found"_s;
break;
case QJsonRpcProtocol::ErrorCode::InvalidParams:
response.errorMessage = u"Invalid Parameters"_s;
break;
case QJsonRpcProtocol::ErrorCode::InternalError:
response.errorMessage = u"Internal Error"_s;
break;
}
response.id = id;
return response;
}
static QJsonObject createParseErrorResponse()
{
return createResponse(QJsonValue::Null,
createPredefinedError(QJsonRpcProtocol::ErrorCode::ParseError));
}
static QJsonObject createInvalidRequestResponse(const QJsonValue &id = QJsonValue::Null)
{
return createResponse(id, createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest));
}
static QJsonObject createMethodNotFoundResponse(const QJsonValue &id)
{
return createResponse(id, createPredefinedError(QJsonRpcProtocol::ErrorCode::MethodNotFound));
}
template<typename Notification>
static QJsonObject createNotification(const Notification ¬ification)
{
QJsonObject object;
object.insert(u"jsonrpc", u"2.0"_s);
object.insert(u"method", notification.method);
object.insert(u"params", notification.params);
return object;
}
class RequestBatchHandler
{
Q_DISABLE_COPY(RequestBatchHandler)
public:
RequestBatchHandler() = default;
RequestBatchHandler(RequestBatchHandler &&) noexcept = default;
RequestBatchHandler &operator=(RequestBatchHandler &&) noexcept = default;
~RequestBatchHandler();
void processMessages(QJsonRpcProtocolPrivate *protocol, const QJsonArray &messages);
private:
QJsonArray m_finished;
QJsonRpcTransport *m_transport = nullptr;
uint m_pending = 0;
bool m_dispatchDone = false;
};
QJsonRpcProtocol::QJsonRpcProtocol() : d(std::make_unique<QJsonRpcProtocolPrivate>()) { }
QJsonRpcProtocol &QJsonRpcProtocol::operator=(QJsonRpcProtocol &&) noexcept = default;
QJsonRpcProtocol::QJsonRpcProtocol(QJsonRpcProtocol &&) noexcept = default;
QJsonRpcProtocol::~QJsonRpcProtocol() = default;
void QJsonRpcProtocol::setMessageHandler(const QString &method,
QJsonRpcProtocol::MessageHandler *handler)
{
d->setMessageHandler(method, std::unique_ptr<QJsonRpcProtocol::MessageHandler>(handler));
}
void QJsonRpcProtocol::setDefaultMessageHandler(QJsonRpcProtocol::MessageHandler *handler)
{
d->setDefaultMessageHandler(std::unique_ptr<QJsonRpcProtocol::MessageHandler>(handler));
}
QJsonRpcProtocol::MessageHandler *QJsonRpcProtocol::messageHandler(const QString &method) const
{
return d->messageHandler(method);
}
QJsonRpcProtocol::MessageHandler *QJsonRpcProtocol::defaultMessageHandler() const
{
return d->defaultMessageHandler();
}
template<typename Request>
static QJsonObject createRequest(const Request &request)
{
QJsonObject object;
object.insert(u"jsonrpc", u"2.0"_s);
object.insert(u"id", request.id);
object.insert(u"method", request.method);
object.insert(u"params", request.params);
return object;
}
void QJsonRpcProtocol::sendRequest(const Request &request,
const QJsonRpcProtocol::Handler<Response> &handler)
{
switch (request.id.type()) {
case QJsonValue::Null:
case QJsonValue::Double:
case QJsonValue::String:
if (d->addPendingRequest(request.id, handler)) {
d->sendMessage(createRequest(request));
return;
}
break;
default:
break;
}
handler(createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest, request.id));
}
void QJsonRpcProtocol::sendNotification(const QJsonRpcProtocol::Notification ¬ification)
{
d->sendMessage(createNotification(notification));
}
void QJsonRpcProtocol::sendBatch(
QJsonRpcProtocol::Batch &&batch,
const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &handler)
{
QJsonArray array;
for (BatchPrivate::Item &item : batch.d->m_items) {
if (item.id.isUndefined()) {
array.append(createNotification(item));
} else {
switch (item.id.type()) {
case QJsonValue::Null:
case QJsonValue::Double:
case QJsonValue::String:
if (d->addPendingRequest(item.id, handler)) {
array.append(createRequest(item));
break;
}
Q_FALLTHROUGH();
default:
handler(createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest,
item.id));
break;
}
}
}
if (!array.isEmpty())
d->sendMessage(array);
}
void QJsonRpcProtocol::setTransport(QJsonRpcTransport *transport)
{
d->setTransport(transport);
}
void QJsonRpcProtocol::setProtocolErrorHandler(const QJsonRpcProtocol::ResponseHandler &handler)
{
d->setProtocolErrorHandler(handler);
}
QJsonRpcProtocol::ResponseHandler QJsonRpcProtocol::protocolErrorHandler() const
{
return d->protocolErrorHandler();
}
void QJsonRpcProtocol::setInvalidResponseHandler(const QJsonRpcProtocol::ResponseHandler &handler)
{
d->setInvalidResponseHandler(handler);
}
QJsonRpcProtocol::ResponseHandler QJsonRpcProtocol::invalidResponseHandler() const
{
return d->invalidResponseHandler();
}
/*!
* \internal
* \typealias QJsonRpcProtocol::MessagePreprocessor
* \brief A function preprocessing incoming messages
*
* A type representing a function receiving a const QJsonDocument &doc, const QJsonParseError
* &error, const QJsonRpcProtocol::Handler<Response> &responseHandler) and returning either
* Processing::Continue or Processing::Stop. Handler can be used to return a response when
* Processing::Stop is returned and doc is a request, i.e. doc.object().contains(u"id") is true.
*/
QJsonRpcProtocol::MessagePreprocessor QJsonRpcProtocol::messagePreprocessor() const
{
return d->messagePreprocessor();
}
void QJsonRpcProtocol::installMessagePreprocessor(const QJsonRpcProtocol::MessagePreprocessor &h)
{
return d->installMessagePreprocessor(h);
}
void QJsonRpcProtocolPrivate::setTransport(QJsonRpcTransport *newTransport)
{
if (newTransport == m_transport)
return;
if (m_transport)
m_transport->setMessageHandler(nullptr);
m_transport = newTransport;
if (m_transport) {
m_transport->setMessageHandler(
[this](const QJsonDocument &message, const QJsonParseError &error) {
processMessage(message, error);
});
}
}
static QJsonRpcProtocol::Request parseRequest(const QJsonObject &object)
{
QJsonRpcProtocol::Request request;
request.id = object.value(u"id");
request.method = object.value(u"method").toString();
request.params = object.value(u"params");
return request;
}
static QJsonRpcProtocol::Notification parseNotification(const QJsonObject &object)
{
QJsonRpcProtocol::Notification notification;
notification.method = object.value(u"method").toString();
notification.params = object.value(u"params");
return notification;
}
void RequestBatchHandler::processMessages(QJsonRpcProtocolPrivate *protocol,
const QJsonArray &messages)
{
m_transport = protocol->transport();
for (const QJsonValue &value : messages) {
if (!value.isObject()) {
m_finished.append(createInvalidRequestResponse());
continue;
}
QJsonObject object = value.toObject();
if (!object.contains(u"method") || !object.value(u"method").isString()) {
m_finished.append(createInvalidRequestResponse());
continue;
}
if (!object.contains(u"id")) {
QJsonRpcProtocol::Notification notification = parseNotification(object);
if (QJsonRpcProtocol::MessageHandler *handler =
protocol->messageHandler(notification.method)) {
handler->handleNotification(notification);
}
continue;
}
QJsonRpcProtocol::Request request = parseRequest(object);
QJsonRpcProtocol::MessageHandler *handler = protocol->messageHandler(request.method);
if (!handler) {
m_finished.append(createMethodNotFoundResponse(request.id));
continue;
}
++m_pending;
const QJsonValue id = request.id;
handler->handleRequest(request, [this, id](const QJsonRpcProtocol::Response &response) {
m_finished.append(createResponse(id, response));
// messages in the batch has finished, so it's safe to delete on the last completion.
if (--m_pending == 0 && m_dispatchDone)
delete this;
});
}
m_dispatchDone = true;
if (m_pending == 0)
delete this;
}
RequestBatchHandler::~RequestBatchHandler()
{
if (m_transport && !m_finished.isEmpty())
m_transport->sendMessage(QJsonDocument(m_finished));
}
void QJsonRpcProtocolPrivate::processRequest(const QJsonObject &object)
{
QJsonRpcProtocol::Request request = parseRequest(object);
if (auto handler = messageHandler(request.method)) {
const QJsonValue id = request.id;
handler->handleRequest(request, [id, this](const QJsonRpcProtocol::Response &response) {
sendMessage(createResponse(id, response));
});
} else {
sendMessage(createMethodNotFoundResponse(request.id));
}
}
void QJsonRpcProtocolPrivate::processResponse(const QJsonObject &object)
{
QJsonRpcProtocol::Response response;
response.id = object.value(u"id");
if (object.contains(u"error")) {
const QJsonObject error = object.value(u"error").toObject();
response.errorCode = error.value(u"code");
response.errorMessage = error.value(u"message").toString();
response.data = error.value(u"data");
} else if (object.contains(u"result")) {
response.data = object.value(u"result");
}
auto pending = m_pendingRequests.find(response.id);
if (pending != m_pendingRequests.end()) {
auto handler = pending->second;
m_pendingRequests.erase(pending);
handler(response);
} else if (response.id.isNull()) {
if (m_protocolErrorHandler)
m_protocolErrorHandler(response);
} else {
if (m_invalidResponseHandler)
m_invalidResponseHandler(response);
}
}
void QJsonRpcProtocolPrivate::processNotification(const QJsonObject &object)
{
QJsonRpcProtocol::Notification notification = parseNotification(object);
if (auto handler = messageHandler(notification.method))
handler->handleNotification(notification);
}
void QJsonRpcProtocolPrivate::processMessage(const QJsonDocument &message,
const QJsonParseError &error)
{
if (m_messagePreprocessor
&& m_messagePreprocessor(message, error,
[message, this](const QJsonRpcProtocol::Response &r) {
Q_ASSERT(message.object().contains(u"id"));
this->sendMessage(
createResponse(message.object().value(u"id"), r));
})
!= QJsonRpcProtocol::Processing::Continue) {
return;
}
if (error.error != QJsonParseError::NoError) {
sendMessage(createParseErrorResponse());
} else if (message.isObject()) {
const QJsonObject object = message.object();
if (object.contains(u"method")) {
if (!object.value(u"method").isString()) {
sendMessage(createInvalidRequestResponse());
} else if (object.contains(u"id")) {
switch (object.value(u"id").type()) {
case QJsonValue::Null:
case QJsonValue::Double:
case QJsonValue::String:
processRequest(object);
break;
default:
sendMessage(createInvalidRequestResponse());
break;
}
} else {
processNotification(object);
}
} else if (object.contains(u"id")) {
processResponse(object);
} else {
sendMessage(createInvalidRequestResponse());
}
} else if (message.isArray()) {
const QJsonArray array = message.array();
if (array.isEmpty()) {
sendMessage(createInvalidRequestResponse());
return;
}
for (const QJsonValue &item : array) {
if (!item.isObject()) {
(new RequestBatchHandler)->processMessages(this, array); // Will delete itself
return;
}
const QJsonObject object = item.toObject();
// If it's not clearly a response, consider the whole batch as request-y
if (object.contains(u"method") || !object.contains(u"id")) {
(new RequestBatchHandler)->processMessages(this, array); // Will delete itself
return;
}
}
// As we haven't returned above, we can consider them all responseses now.
for (const QJsonValue &item : array)
processResponse(item.toObject());
} else {
sendMessage(createInvalidRequestResponse());
}
}
QJsonRpcProtocol::MessageHandler::MessageHandler() = default;
QJsonRpcProtocol::MessageHandler::~MessageHandler() = default;
void QJsonRpcProtocol::MessageHandler::handleRequest(const QJsonRpcProtocol::Request &request,
const ResponseHandler &handler)
{
Q_UNUSED(request);
handler(error(QJsonRpcProtocol::ErrorCode::MethodNotFound));
}
void QJsonRpcProtocol::MessageHandler::handleNotification(
const QJsonRpcProtocol::Notification ¬ification)
{
Q_UNUSED(notification);
}
QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::error(QJsonRpcProtocol::ErrorCode code)
{
return createPredefinedError(code);
}
QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::error(int code, const QString &message,
const QJsonValue &data)
{
QJsonRpcProtocol::Response response;
response.errorCode = code;
response.errorMessage = message;
response.data = data;
return response;
}
QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::result(const QJsonValue &result)
{
QJsonRpcProtocol::Response response;
response.data = result;
return response;
}
QJsonRpcProtocol::Batch::Batch() : d(std::make_unique<BatchPrivate>()) { }
QJsonRpcProtocol::Batch::~Batch() = default;
QJsonRpcProtocol::Batch::Batch(QJsonRpcProtocol::Batch &&) noexcept = default;
QJsonRpcProtocol::Batch &
QJsonRpcProtocol::Batch::operator=(QJsonRpcProtocol::Batch &&) noexcept = default;
void QJsonRpcProtocol::Batch::addNotification(const Notification ¬ification)
{
BatchPrivate::Item item;
item.method = notification.method;
item.params = notification.params;
d->m_items.push_back(std::move(item));
}
void QJsonRpcProtocol::Batch::addRequest(const Request &request)
{
BatchPrivate::Item item;
item.id = request.id;
item.method = request.method;
item.params = request.params;
d->m_items.push_back(std::move(item));
}
QT_END_NAMESPACE