This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Simon Stürz 2acf7784a6 Update the entire Qt6 code to new signal slot connections
Update logging cathegories and allign coding style
2025-09-11 10:09:24 +02:00

332 lines
14 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "jsonvalidator.h"
#include "jsonrpc/jsonhandler.h"
#include "loggingcategories.h"
#include <QJsonDocument>
#include <QColor>
#include <QDateTime>
#include <QRegularExpression>
namespace nymeaserver {
bool JsonValidator::checkRefs(const QVariantMap &map, const QVariantMap &api)
{
QVariantMap enums = api.value("enums").toMap();
QVariantMap flags = api.value("flags").toMap();
QVariantMap types = api.value("types").toMap();
foreach (const QString &key, map.keys()) {
if (map.value(key).toString().startsWith("$ref:")) {
QString refName = map.value(key).toString().remove("$ref:");
if (!enums.contains(refName) && !flags.contains(refName) && !types.contains(refName)) {
qCWarning(dcJsonRpc()) << "Invalid reference to" << refName << "in" << key << map;
return false;
}
}
if (map.value(key).userType() == QMetaType::QVariantMap) {
bool ret = checkRefs(map.value(key).toMap(), api);
if (!ret) {
return false;
}
}
if (map.value(key).userType() == QMetaType::QVariantList) {
foreach (const QVariant &entry, map.value(key).toList()) {
if (entry.toString().startsWith("$ref:")) {
QString refName = entry.toString().remove("$ref:");
if (!enums.contains(refName) && !flags.contains(refName) && !types.contains(refName)) {
qCWarning(dcJsonRpc()) << "Invalid reference to" << refName << "in" << key << map;
return false;
}
}
if (entry.userType() == QMetaType::QVariantMap) {
bool ret = checkRefs(map.value(key).toMap(), api);
if (!ret) {
return false;
}
}
}
}
}
return true;
}
JsonValidator::Result JsonValidator::validateParams(const QVariantMap &params, const QString &method, const QVariantMap &api)
{
QVariantMap paramDefinition = api.value("methods").toMap().value(method).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api, QIODevice::WriteOnly);
m_result.setWhere(method + ", param " + m_result.where());
return m_result;
}
JsonValidator::Result JsonValidator::validateReturns(const QVariantMap &returns, const QString &method, const QVariantMap &api)
{
QVariantMap returnsDefinition = api.value("methods").toMap().value(method).toMap().value("returns").toMap();
m_result = validateMap(returns, returnsDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(method + ", returns " + m_result.where());
return m_result;
}
JsonValidator::Result JsonValidator::validateNotificationParams(const QVariantMap &params, const QString &notification, const QVariantMap &api)
{
QVariantMap paramDefinition = api.value("notifications").toMap().value(notification).toMap().value("params").toMap();
m_result = validateMap(params, paramDefinition, api, QIODevice::ReadOnly);
m_result.setWhere(notification + ", param " + m_result.where());
return m_result;
}
JsonValidator::Result JsonValidator::result() const
{
return m_result;
}
JsonValidator::Result JsonValidator::validateMap(const QVariantMap &map, const QVariantMap &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{
// Make sure all required values are available
foreach (const QString &key, definition.keys()) {
QRegularExpression isOptional = QRegularExpression("^([a-z]:)*o:.*");
if (isOptional.match(key).hasMatch()) {
continue;
}
QRegularExpression isReadOnly = QRegularExpression("^([a-z]:)*r:.*");
if (isReadOnly.match(key).hasMatch() && openMode.testFlag(QIODevice::WriteOnly)) {
continue;
}
QString trimmedKey = key;
trimmedKey.remove(QRegularExpression("^(o:|r:|d:)*"));
if (!map.contains(trimmedKey)) {
return Result(false, "Missing required key: " + key, key);
}
}
// Make sure given values are valid
foreach (const QString &key, map.keys()) {
// Is the key allowed in here?
QVariant expectedValue = definition.value(key);
foreach (const QString &definitionKey, definition.keys()) {
QRegularExpression regExp = QRegularExpression("(o:|r:|d:)" + key);
if (regExp.match(definitionKey).hasMatch()) {
expectedValue = definition.value(definitionKey);
break;
}
}
if (!expectedValue.isValid()) {
expectedValue = definition.value("o:" + key);
}
if (!expectedValue.isValid()) {
expectedValue = definition.value("d:" + key);
}
if (!expectedValue.isValid()) {
return Result(false, "Invalid key: " + key);
}
// Validate content
QVariant value = map.value(key);
Result result = validateEntry(value, expectedValue, api, openMode);
if (!result.success()) {
result.setWhere(key + '.' + result.where());
result.setErrorString(result.errorString());
return result;
}
}
return Result(true);
}
JsonValidator::Result JsonValidator::validateEntry(const QVariant &value, const QVariant &definition, const QVariantMap &api, QIODevice::OpenMode openMode)
{
if (definition.userType() == QMetaType::QString) {
QString expectedTypeName = definition.toString();
if (expectedTypeName.startsWith("$ref:")) {
QString refName = expectedTypeName;
refName.remove("$ref:");
// Refs might be enums
QVariantMap enums = api.value("enums").toMap();
if (enums.contains(refName)) {
QVariant refDefinition = enums.value(refName);
QVariantList enumList = refDefinition.toList();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (value.metaType().id() == QMetaType::QStringList) {
foreach (const QString &valueString, value.toStringList()) {
if (!enumList.contains(valueString)) {
return Result(false, "Expected enum value for " + refName + " but got " + value.toString());
}
}
} else {
if (!enumList.contains(value.toString())) {
return Result(false, "Expected enum value for " + refName + " but got " + value.toString());
}
}
#else
if (!enumList.contains(value.toString())) {
return Result(false, "Expected enum value for " + refName + " but got " + value.toString());
}
#endif
return Result(true);
}
// Or flags
QVariantMap flags = api.value("flags").toMap();
if (flags.contains(refName)) {
QVariant refDefinition = flags.value(refName);
if (value.userType() != QMetaType::QVariantList && value.userType() != QMetaType::QStringList) {
return Result(false, "Expected flags " + refName + " but got " + value.toString());
}
QString flagEnum = refDefinition.toList().first().toString();
foreach (const QVariant &flagsEntry, value.toList()) {
Result result = validateEntry(flagsEntry, flagEnum, api, openMode);
if (!result.success()) {
return result;
}
}
return Result(true);
}
QVariantMap types = api.value("types").toMap();
QVariant refDefinition = types.value(refName);
return validateEntry(value, refDefinition, api, openMode);
}
JsonHandler::BasicType expectedBasicType = JsonHandler::enumNameToValue<JsonHandler::BasicType>(expectedTypeName);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// Any string converts fine to Uuid, but the resulting uuid might be null
if (expectedBasicType == JsonHandler::Uuid && value.toUuid().isNull()) {
QString typeName(value.typeName());
//qCCritical(dcJsonRpc()) << value << value.userType() << value.typeId() << value.typeName() << value.toString() << value.toUuid() << value.canConvert(QMetaType::QUuid);
// Verify if this is one of our own uuid types
if (typeName == "ThingId" || typeName == "EventTypeId" || typeName == "StateTypeId" || typeName == "ActionTypeId") {
return Result(true);
}/* else {
return Result(false, "Invalid Uuid: " + value.toString());
}*/
}
#else
QMetaType::Type expectedVariantType = JsonHandler::basicTypeToMetaType(expectedBasicType);
// Verify basic compatiblity
if (expectedBasicType != JsonHandler::Variant && !value.canConvert(expectedVariantType)) {
return Result(false, "Invalid value. Expected: " + definition.toString() + ", Got: " + value.toString());
}
// Any string converts fine to Uuid, but the resulting uuid might be null
if (expectedBasicType == JsonHandler::Uuid && value.toUuid().isNull()) {
return Result(false, "Invalid Uuid: " + value.toString());
}
#endif
// Make sure ints are valid
if (expectedBasicType == JsonHandler::Int) {
bool ok;
value.toLongLong(&ok);
if (!ok) {
return Result(false, "Invalid Int: " + value.toString());
}
}
// UInts
if (expectedBasicType == JsonHandler::Uint) {
bool ok;
value.toULongLong(&ok);
if (!ok) {
return Result(false, "Invalid UInt: " + value.toString());
}
}
// Double
if (expectedBasicType == JsonHandler::Double) {
bool ok;
value.toDouble(&ok);
if (!ok) {
return Result(false, "Invalid Double: " + value.toString());
}
}
// Color
if (expectedBasicType == JsonHandler::Color) {
QColor color = value.value<QColor>();
if (!color.isValid()) {
return Result(false, "Invalid Color: " + value.toString());
}
}
// Time
if (expectedBasicType == JsonHandler::Time) {
QTime time = QTime::fromString(value.toString(), "hh:mm");
if (!time.isValid()) {
return Result(false, "Invalid Time: " + value.toString());
}
}
return Result(true);
}
if (definition.userType() == QMetaType::QVariantMap) {
if (value.userType() != QMetaType::QVariantMap) {
return Result(false, "Invalid value. Expected a map but received: " + value.toString());
}
return validateMap(value.toMap(), definition.toMap(), api, openMode);
}
if (definition.userType() == QMetaType::QVariantList) {
QVariantList list = definition.toList();
QVariant entryDefinition = list.first();
if (value.userType() != QMetaType::QVariantList && value.userType() != QMetaType::QStringList) {
QString elementType = QString(value.typeName()).remove("QList<").remove(">");
if (elementType == "ThingId" || elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") {
elementType = "Uuid";
} /*else if (elementType == "ParamList") {
elementType = "Param";
} else {
return Result(false, "Expected list of " + entryDefinition.toString() + " but got value of type " + value.typeName() + "\n" + QJsonDocument::fromVariant(value).toJson());
}*/
}
foreach (const QVariant &entry, value.toList()) {
Result result = validateEntry(entry, entryDefinition, api, openMode);
if (!result.success()) {
return result;
}
}
return Result(true);
}
Q_ASSERT_X(false, "JsonValildator", QString("Incomplete validation. Unexpected type %1 in template").arg(definition.type()).toUtf8());
return Result(false);
}
}