Revision control
++ // 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