From 1df02b5f980b01a4e42f32061f1cba696b6a22e9 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Fri, 7 Aug 2020 14:57:11 +0200 Subject: Fix conversions to JSON from QVariant After reimplementing Qt JSON support on top of CBOR, there were unintended behavior changes when converting QVariant{, List, Map} to QJson{Value, Array, List} due to reusing the code for converting QVariant* types to CBOR types, and from CBOR types to corresponding JSON types. In particular, conversions from QVariant containing QByteArray to JSON has been affected: according to RFC 7049, when converting from CBOR to JSON, raw byte array data must be encoded in base64url when converting to a JSON string. As a result QVariant* types containing QByteArray data ended up base64url-encoded when converted to JSON, instead of converting using QString::fromUtf8() as before. There were also differences when converting QRegularExpression. Reverted the behavior changes by adding a flag to internal methods for converting CBOR to JSON, to distinguish whether the conversion is done from QVariant* or CBOR types. These methods now will fall back to the old behavior, if the conversion is done using QJson*::fromVariant*(). Additionally fixed QJsonValue::fromVariant conversion for NaN and infinities: they should always convert to QJsonValue::Null. This works correctly when converting from variant to QJsonArray/QJsonObject, but has been wrong for QJsonValue. Added more tests to verify the expected behavior. [ChangeLog][Important Behavior Changes] Restored pre-5.15.0 behavior when converting from QVariant* to QJson* types. Unforeseen consequences of changes in 5.15.0 caused QByteArray data to be base64url-encoded; the handling of QRegularExpression was also unintentionally changed. These conversions are now reverted to the prior behavior. Additionally fixed QJsonValue::fromVariant conversions for NaN and infinities: they should always convert to QJsonValue::Null. Fixes: QTBUG-84739 Change-Id: Iaee667d00e5363906eedbb67948b7b39c9d0bc78 Reviewed-by: Thiago Macieira --- src/corelib/serialization/qjsoncbor.cpp | 58 +++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) (limited to 'src/corelib/serialization/qjsoncbor.cpp') diff --git a/src/corelib/serialization/qjsoncbor.cpp b/src/corelib/serialization/qjsoncbor.cpp index e0eab74a02..e836935f6f 100644 --- a/src/corelib/serialization/qjsoncbor.cpp +++ b/src/corelib/serialization/qjsoncbor.cpp @@ -55,6 +55,8 @@ QT_BEGIN_NAMESPACE using namespace QtCbor; +enum class ConversionMode { FromRaw, FromVariantToJson }; + static QJsonValue fpToJson(double v) { return qt_is_finite(v) ? QJsonValue(v) : QJsonValue(); @@ -89,7 +91,8 @@ static QString encodeByteArray(const QCborContainerPrivate *d, qsizetype idx, QC return QString::fromLatin1(data, data.size()); } -static QString makeString(const QCborContainerPrivate *d, qsizetype idx); +static QString makeString(const QCborContainerPrivate *d, qsizetype idx, + ConversionMode mode = ConversionMode::FromRaw); static QString maybeEncodeTag(const QCborContainerPrivate *d) { @@ -134,7 +137,8 @@ static QString encodeTag(const QCborContainerPrivate *d) return s; } -static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizetype idx) +static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizetype idx, + ConversionMode mode) { const auto &e = d->elements.at(idx); @@ -146,7 +150,9 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety return QString::number(e.fpvalue()); case QCborValue::ByteArray: - return encodeByteArray(d, idx, QCborTag(QCborKnownTags::ExpectedBase64url)); + return mode == ConversionMode::FromVariantToJson + ? d->stringAt(idx) + : encodeByteArray(d, idx, QCborTag(QCborKnownTags::ExpectedBase64url)); case QCborValue::String: return d->stringAt(idx); @@ -190,7 +196,8 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety return simpleTypeString(e.type); } -QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx); +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx, + ConversionMode mode = ConversionMode::FromRaw); static QJsonValue convertExtendedTypeToJson(QCborContainerPrivate *d) { @@ -222,35 +229,37 @@ static QJsonValue convertExtendedTypeToJson(QCborContainerPrivate *d) } // We need to do this because sub-objects may need conversion. -static QJsonArray convertToJsonArray(QCborContainerPrivate *d) +static QJsonArray convertToJsonArray(QCborContainerPrivate *d, + ConversionMode mode = ConversionMode::FromRaw) { QJsonArray a; if (d) { for (qsizetype idx = 0; idx < d->elements.size(); ++idx) - a.append(qt_convertToJson(d, idx)); + a.append(qt_convertToJson(d, idx, mode)); } return a; } // We need to do this because the keys need to be sorted and converted to strings // and sub-objects may need recursive conversion. -static QJsonObject convertToJsonObject(QCborContainerPrivate *d) +static QJsonObject convertToJsonObject(QCborContainerPrivate *d, + ConversionMode mode = ConversionMode::FromRaw) { QJsonObject o; if (d) { for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) - o.insert(makeString(d, idx), qt_convertToJson(d, idx + 1)); + o.insert(makeString(d, idx), qt_convertToJson(d, idx + 1, mode)); } return o; } -QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx, ConversionMode mode) { // encoding the container itself if (idx == -QCborValue::Array) - return convertToJsonArray(d); + return convertToJsonArray(d, mode); if (idx == -QCborValue::Map) - return convertToJsonObject(d); + return convertToJsonObject(d, mode); if (idx < 0) { // tag-like type if (!d || d->elements.size() != 2) @@ -264,6 +273,15 @@ QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) case QCborValue::Integer: return QJsonValue(e.value); case QCborValue::ByteArray: + if (mode == ConversionMode::FromVariantToJson) { + const auto value = makeString(d, idx, mode); + return value.isEmpty() ? QJsonValue() : QJsonPrivate::Value::fromTrustedCbor(value); + } + break; + case QCborValue::RegularExpression: + if (mode == ConversionMode::FromVariantToJson) + return QJsonValue(); + break; case QCborValue::String: case QCborValue::SimpleType: // make string @@ -274,10 +292,10 @@ QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) case QCborValue::Tag: case QCborValue::DateTime: case QCborValue::Url: - case QCborValue::RegularExpression: case QCborValue::Uuid: // recurse - return qt_convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); + return qt_convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type, + mode); case QCborValue::Null: case QCborValue::Undefined: @@ -294,7 +312,7 @@ QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) return fpToJson(e.fpvalue()); } - return QJsonPrivate::Value::fromTrustedCbor(makeString(d, idx)); + return QJsonPrivate::Value::fromTrustedCbor(makeString(d, idx, mode)); } /*! @@ -429,6 +447,12 @@ QJsonArray QCborArray::toJsonArray() const return convertToJsonArray(d.data()); } +QJsonArray QJsonPrivate::Variant::toJsonArray(const QVariantList &list) +{ + const auto cborArray = QCborArray::fromVariantList(list); + return convertToJsonArray(cborArray.d.data(), ConversionMode::FromVariantToJson); +} + /*! Recursively converts every \l QCborValue value in this map to JSON using QCborValue::toJsonValue() and creates a string key for all keys that aren't @@ -471,6 +495,12 @@ QJsonObject QCborMap::toJsonObject() const return convertToJsonObject(d.data()); } +QJsonObject QJsonPrivate::Variant::toJsonObject(const QVariantMap &map) +{ + const auto cborMap = QCborMap::fromVariantMap(map); + return convertToJsonObject(cborMap.d.data(), ConversionMode::FromVariantToJson); +} + /*! Converts this value to a native Qt type and returns the corresponding QVariant. -- cgit v1.2.3