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 "qtypedjson_p.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qjsonarray.h>
#include <cstring>
QT_BEGIN_NAMESPACE
namespace QTypedJson {
Q_LOGGING_CATEGORY(jsonRpcLog, "qt.jsonrpc");
Reader::Reader(const QJsonValue &v)
: m_p(new ReaderPrivate { QList({ ValueStack { v, QString(), -1, 0 } }) })
{
}
Reader::~Reader()
{
for (const QString &msg : std::as_const(m_p->errorMessages))
qCWarning(jsonRpcLog) << msg;
delete m_p;
}
QStringList Reader::errorMessages()
{
return m_p->errorMessages;
}
void Reader::clearErrorMessages()
{
m_p->errorMessages.clear();
}
void Reader::handleBasic(bool &el)
{
if (currentValue().isBool())
el = currentValue().toBool();
else
warnMissing(u"bool");
}
void Reader::handleBasic(QByteArray &el)
{
if (currentValue().isString())
el = currentValue().toString().toUtf8();
else
warnMissing(u"string");
}
void Reader::handleConstString(QByteArrayView el)
{
if (!currentValue().isString() || el != currentValue().toString().toUtf8())
warnMissing(u"string");
}
void Reader::handleBasic(int &el)
{
if (currentValue().isDouble())
el = currentValue().toInt(el);
else
warnMissing(u"int");
}
void Reader::handleBasic(unsigned int &el)
{
if (currentValue().isDouble()) {
double currentInt = currentValue().toDouble(el);
if (currentInt < 0 || currentInt > std::numeric_limits<uint>::max()) {
warn(QStringLiteral("Value %1 does not fit the unsigned int range of [0, %2].")
.arg(QString::number(currentInt),
QString::number(std::numeric_limits<uint>::max())));
return;
}
el = static_cast<unsigned int>(currentInt);
} else {
warnMissing(u"unsigned int");
}
}
void Reader::handleBasic(double &el)
{
if (currentValue().isDouble())
el = currentValue().toDouble();
else
warnMissing(u"double");
}
void Reader::handleNullType()
{
if (!currentValue().isNull() && !currentValue().isUndefined()) {
warnNonNull();
}
}
bool Reader::startField(const QString &fieldName)
{
int oldWarnLevel = (m_p->valuesStack.isEmpty() ? 0 : m_p->valuesStack.last().warnLevel);
m_p->objectsStack.last().visitedFields.insert(fieldName);
m_p->valuesStack.append(ValueStack { currentValue()[fieldName], fieldName, -1,
(oldWarnLevel ? oldWarnLevel + 1 : 0) });
return true;
}
bool Reader::startField(const char *fieldName)
{
QString f = QString::fromUtf8(fieldName); // conversion needed just to set the fieldPath
return startField(f);
}
bool Reader::startField(const QByteArray &fieldName)
{
return startField(fieldName.data());
}
void Reader::endField(const QString &fieldName)
{
Q_ASSERT(m_p->valuesStack.last().fieldPath == fieldName);
m_p->valuesStack.removeLast();
}
void Reader::endField(const char *fieldName)
{
QString f = QString::fromUtf8(fieldName);
endField(f);
}
void Reader::endField(const QByteArray &fieldName)
{
endField(fieldName.data());
}
bool Reader::startObjectF(const char *type, ObjectOptions options, quintptr)
{
if (m_p->parseStatus != ParseStatus::Normal)
return false;
if (currentValue().isUndefined()) {
m_p->parseStatus = ParseStatus::Failed;
return false;
}
m_p->objectsStack.append(ObjectStack { type, options, {} });
return true;
}
void Reader::endObjectF(const char *type, ObjectOptions, quintptr)
{
Q_ASSERT(std::strcmp(m_p->objectsStack.last().type, type) == 0);
m_p->objectsStack.removeLast();
}
void Reader::warnExtra(const QJsonObject &e)
{
if (e.constBegin() != e.constEnd())
warn(QStringLiteral(u"%1 has extra fields %2")
.arg(currentPath(), QString::fromUtf8(QJsonDocument(e).toJson())));
}
void Reader::warnInvalidSize(qint32 size, qint32 expectedSize)
{
if (size != expectedSize)
warn(QStringLiteral(u"%1 expected %1 elements, not %2.")
.arg(currentPath(), QString::number(expectedSize), QString::number(size)));
}
void Reader::warnMissing(QStringView s)
{
warn(QStringLiteral(u"%1 misses value of type %2").arg(currentPath(), s));
}
void Reader::warnNonNull()
{
QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson();
warn(QStringLiteral(u"%1 is supposed to be null, but is %2")
.arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2))));
}
void Reader::warn(const QString &msg)
{
m_p->errorMessages.append(msg);
m_p->parseStatus = ParseStatus::Failed;
}
void Reader::handleJson(QJsonValue &v)
{
v = currentValue();
}
void Reader::handleJson(QJsonObject &v)
{
if (!currentValue().isObject() && !currentValue().isNull() && !currentValue().isUndefined()) {
QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson();
warn(QStringLiteral(u"Error: expected an object at %1, not %2")
.arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2))));
}
v = currentValue().toObject();
}
void Reader::handleJson(QJsonArray &v)
{
if (!currentValue().isArray() && !currentValue().isNull() && !currentValue().isUndefined()) {
QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson();
warn(QStringLiteral(u"Error: expected an array at %1, not %2")
.arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2))));
}
v = currentValue().toArray();
}
QJsonObject Reader::getExtraFields() const
{
QJsonObject extraFields;
QJsonObject v = currentValue().toObject();
auto it = v.constBegin();
auto end = v.constEnd();
auto &vField = m_p->objectsStack.last().visitedFields;
while (it != end) {
if (!vField.contains(it.key())) {
extraFields.insert(it.key(), it.value());
}
++it;
}
return extraFields;
}
void Reader::startArrayF(qint32 &size)
{
size = int(currentValue().toArray().size());
}
bool Reader::startElement(qint32 index)
{
int oldWarnLevel = (m_p->valuesStack.isEmpty() ? 0 : m_p->valuesStack.last().warnLevel);
m_p->valuesStack.append(ValueStack { currentValue().toArray().at(index), QString(), index,
(oldWarnLevel ? oldWarnLevel + 1 : 0) });
return true;
}
void Reader::endElement(qint32 index)
{
Q_ASSERT(m_p->valuesStack.last().indexPath == index);
m_p->valuesStack.removeLast();
}
void Reader::endArrayF(qint32 &) { }
QString Reader::currentPath() const
{
QStringList res;
for (const auto &el : std::as_const(m_p->valuesStack)) {
if (el.indexPath != -1)
res.append(QString::number(el.indexPath));
else
res.append(el.fieldPath);
}
return res.join(u".");
}
bool Reader::startTuple(qint32 size)
{
qint32 expected = qint32(currentValue().toArray().size());
if (size != expected) {
warnInvalidSize(size, expected);
return false;
};
return true;
}
void Reader::endTuple(qint32) { }
void JsonBuilder::handleBasic(const bool &v)
{
m_values.append(QJsonValue(v));
}
void JsonBuilder::handleBasic(const QByteArray &v)
{
handleConstString(v);
}
void JsonBuilder::handleConstString(QByteArrayView v)
{
m_values.append(QJsonValue(QString::fromUtf8(v)));
}
void JsonBuilder::handleBasic(const int &v)
{
m_values.append(QJsonValue(v));
}
void JsonBuilder::handleBasic(const unsigned int &v)
{
// note: there is no unsigned representation in QJsonValue.
handleBasic(static_cast<double>(v));
}
void JsonBuilder::handleBasic(const double &v)
{
m_values.append(QJsonValue(v));
}
void JsonBuilder::handleNullType()
{
m_values.append(QJsonValue(QJsonValue::Type::Null));
}
void JsonBuilder::handleMissingOptional()
{
if (m_fieldLevel.isEmpty() || m_fieldLevel.last() != m_values.size())
handleNullType();
}
bool JsonBuilder::startField(const QString &)
{
m_fieldLevel.append(m_values.size());
return true;
}
bool JsonBuilder::startField(const QByteArray &)
{
return startField(nullptr);
}
bool JsonBuilder::startField(const char *)
{
m_fieldLevel.append(m_values.size());
return true;
}
void JsonBuilder::endField(const QString &v)
{
Q_ASSERT(!m_fieldLevel.isEmpty());
if (m_fieldLevel.last() < m_values.size()) {
Q_ASSERT(m_values.size() > 1);
if (QJsonObject *o = std::get_if<QJsonObject>(&m_values[m_values.size() - 2])) {
o->insert(v, popLastValue());
} else {
Q_ASSERT(false);
}
}
Q_ASSERT(!m_fieldLevel.isEmpty() && m_fieldLevel.last() == m_values.size());
m_fieldLevel.removeLast();
}
void JsonBuilder::endField(const char *v)
{
endField(QString::fromUtf8(v));
}
void JsonBuilder::endField(const QByteArray &v)
{
endField(v.data());
}
bool JsonBuilder::startObjectF(const char *, ObjectOptions, quintptr)
{
m_values.append(QJsonObject());
return true;
}
void JsonBuilder::endObjectF(const char *, ObjectOptions, quintptr) { }
bool JsonBuilder::startArrayF(qint32 &)
{
m_values.append(QJsonArray());
m_arrayLevel.append(m_values.size());
return true;
}
bool JsonBuilder::startElement(qint32)
{
return true;
}
void JsonBuilder::endElement(qint32)
{
Q_ASSERT(m_values.size() > 1);
if (QJsonArray *a = std::get_if<QJsonArray>(&m_values[m_values.size() - 2])) {
a->append(popLastValue());
} else {
Q_ASSERT(false);
}
}
void JsonBuilder::endArrayF(qint32 &)
{
Q_ASSERT(!m_arrayLevel.isEmpty() && m_arrayLevel.last() == m_values.size());
m_arrayLevel.removeLast();
}
void JsonBuilder::handleJson(QJsonValue &v)
{
m_values.append(v);
}
void JsonBuilder::handleJson(QJsonObject &v)
{
m_values.append(v);
}
void JsonBuilder::handleJson(QJsonArray &v)
{
m_values.append(v);
}
QJsonValue JsonBuilder::popLastValue()
{
if (m_values.isEmpty())
return QJsonValue(QJsonValue::Type::Undefined);
QJsonValue res = std::visit([](auto &v) { return QJsonValue(v); }, m_values.last());
m_values.removeLast();
return res;
}
bool JsonBuilder::startTuple(qint32 size)
{
return startArrayF(size);
}
void JsonBuilder::endTuple(qint32 size)
{
endArrayF(size);
}
} // namespace QTypedJson
QT_END_NAMESPACE