QCborValue: Extend the constructor to also create extended types

We already did that when parsing from CBOR binary data, so the code was
already present.

[ChangeLog][QtCore][QCborValue] The constructor taking a CBOR tag and a
value to be tagged now attempts to convert to a QCborValue extended
type. For example, if the tag is 0 (UnixTime_t) and the payload is a
number, the resulting object will become tag 1 (DateTime) and the
payload will be the the ISO-8601 date/time string.

Fixes: QTBUG-79196
Change-Id: I6edce5101800424a8093fffd15cdf650fb2fc45c
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
bb10
Thiago Macieira 2019-10-15 16:37:52 -07:00
parent 8027fb60dd
commit bcbefcd645
2 changed files with 142 additions and 86 deletions

View File

@ -758,6 +758,81 @@ QT_BEGIN_NAMESPACE
using namespace QtCbor;
static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d)
{
qint64 tag = d->elements.at(0).value;
auto &e = d->elements[1];
const ByteData *b = d->byteData(e);
auto replaceByteData = [&](const char *buf, qsizetype len, Element::ValueFlags f) {
d->data.clear();
d->usedData = 0;
e.flags = Element::HasByteData | f;
e.value = d->addByteData(buf, len);
};
switch (tag) {
case qint64(QCborKnownTags::DateTimeString):
case qint64(QCborKnownTags::UnixTime_t): {
QDateTime dt;
if (tag == qint64(QCborKnownTags::DateTimeString) && b &&
e.type == QCborValue::String && (e.flags & Element::StringIsUtf16) == 0) {
// The data is supposed to be US-ASCII. If it isn't (contains UTF-8),
// QDateTime::fromString will fail anyway.
dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs);
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) {
dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC);
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) {
dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC);
}
if (dt.isValid()) {
QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1();
replaceByteData(text, text.size(), Element::StringIsAscii);
e.type = QCborValue::String;
d->elements[0].value = qint64(QCborKnownTags::DateTimeString);
return QCborValue::DateTime;
}
break;
}
case qint64(QCborKnownTags::Url):
if (e.type == QCborValue::String) {
if (b) {
// normalize to a short (decoded) form, so as to save space
QUrl url(e.flags & Element::StringIsUtf16 ?
b->asQStringRaw() :
b->toUtf8String());
QByteArray encoded = url.toString(QUrl::DecodeReserved).toUtf8();
replaceByteData(encoded, encoded.size(), {});
}
return QCborValue::Url;
}
break;
case quint64(QCborKnownTags::RegularExpression):
if (e.type == QCborValue::String) {
// no normalization is necessary
return QCborValue::RegularExpression;
}
break;
case qint64(QCborKnownTags::Uuid):
if (e.type == QCborValue::ByteArray) {
// force the size to 16
char buf[sizeof(QUuid)] = {};
if (b)
memcpy(buf, b->byte(), qMin(sizeof(buf), size_t(b->len)));
replaceByteData(buf, sizeof(buf), {});
return QCborValue::Uuid;
}
break;
}
// no enriching happened
return QCborValue::Tag;
}
// in qcborstream.cpp
extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error);
@ -1384,77 +1459,10 @@ static QCborValue taggedValueFromCbor(QCborStreamReader &reader)
d->decodeValueFromCbor(reader);
}
QCborValue::Type type = QCborValue::Tag;
QCborValue::Type type;
if (reader.lastError() == QCborError::NoError) {
// post-process to create our extended types
qint64 tag = d->elements.at(0).value;
auto &e = d->elements[1];
const ByteData *b = d->byteData(e);
auto replaceByteData = [&](const char *buf, qsizetype len, Element::ValueFlags f) {
d->data.clear();
d->usedData = 0;
e.flags = Element::HasByteData | f;
e.value = d->addByteData(buf, len);
};
switch (tag) {
case qint64(QCborKnownTags::DateTimeString):
case qint64(QCborKnownTags::UnixTime_t): {
QDateTime dt;
if (tag == qint64(QCborKnownTags::DateTimeString) && b &&
e.type == QCborValue::String && (e.flags & Element::StringIsUtf16) == 0) {
// The data is supposed to be US-ASCII. If it isn't,
// QDateTime::fromString will fail anyway.
dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs);
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) {
dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC);
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) {
dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC);
}
if (dt.isValid()) {
QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1();
replaceByteData(text, text.size(), Element::StringIsAscii);
e.type = QCborValue::String;
d->elements[0].value = qint64(QCborKnownTags::DateTimeString);
type = QCborValue::DateTime;
}
break;
}
case qint64(QCborKnownTags::Url):
if (e.type == QCborValue::String) {
if (b) {
// normalize to a short (decoded) form, so as to save space
QUrl url(e.flags & Element::StringIsUtf16 ?
b->asQStringRaw() :
b->toUtf8String());
QByteArray encoded = url.toString(QUrl::DecodeReserved).toUtf8();
replaceByteData(encoded, encoded.size(), {});
}
type = QCborValue::Url;
}
break;
case quint64(QCborKnownTags::RegularExpression):
if (e.type == QCborValue::String) {
// no normalization is necessary
type = QCborValue::RegularExpression;
}
break;
case qint64(QCborKnownTags::Uuid):
if (e.type == QCborValue::ByteArray) {
// force the size to 16
char buf[sizeof(QUuid)] = {};
if (b)
memcpy(buf, b->byte(), qMin(sizeof(buf), size_t(b->len)));
replaceByteData(buf, sizeof(buf), {});
type = QCborValue::Uuid;
}
break;
}
type = convertToExtendedType(d);
} else {
// decoding error
type = QCborValue::Invalid;
@ -1717,21 +1725,22 @@ QCborValue::QCborValue(const QCborMap &m)
}
/*!
\fn QCborValue::QCborValue(QCborTag t, const QCborValue &tv)
\fn QCborValue::QCborValue(QCborKnownTags t, const QCborValue &tv)
\fn QCborValue::QCborValue(QCborTag tag, const QCborValue &tv)
\fn QCborValue::QCborValue(QCborKnownTags tag, const QCborValue &tv)
Creates a QCborValue for the extended type represented by the tag value \a
t, tagging value \a tv. The tag can later be retrieved using tag() and
tag, tagging value \a tv. The tag can later be retrieved using tag() and
the tagged value using taggedValue().
\sa isTag(), tag(), taggedValue(), QCborKnownTags
*/
QCborValue::QCborValue(QCborTag t, const QCborValue &tv)
QCborValue::QCborValue(QCborTag tag, const QCborValue &tv)
: n(-1), container(new QCborContainerPrivate), t(Tag)
{
container->ref.storeRelaxed(1);
container->append(t);
container->append(tag);
container->append(tv);
t = convertToExtendedType(container);
}
/*!

View File

@ -40,6 +40,7 @@
#include <QtCore/qcborvalue.h>
#include <QtTest>
Q_DECLARE_METATYPE(QCborKnownTags)
Q_DECLARE_METATYPE(QCborValue)
Q_DECLARE_METATYPE(QCborValue::EncodingOptions)
@ -294,34 +295,77 @@ void tst_QCborValue::tagged()
void tst_QCborValue::extendedTypes_data()
{
QTest::addColumn<QCborValue>("extended");
QTest::addColumn<QCborValue>("tagged");
QTest::addColumn<QCborKnownTags>("tag");
QTest::addColumn<QCborValue>("taggedValue");
QTest::addColumn<QCborValue>("correctedTaggedValue");
QCborValue v(QCborValue::Invalid);
QDateTime dt = QDateTime::currentDateTimeUtc();
QDateTime dtTzOffset(dt.date(), dt.time(), Qt::OffsetFromUTC, dt.offsetFromUtc());
QUuid uuid = QUuid::createUuid();
// non-correcting extended types (tagged value remains unchanged)
QTest::newRow("DateTime") << QCborValue(dt)
<< QCborValue(QCborKnownTags::DateTimeString, dt.toString(Qt::ISODateWithMs));
<< QCborKnownTags::DateTimeString << QCborValue(dt.toString(Qt::ISODateWithMs)) << v;
QTest::newRow("DateTime:TzOffset") << QCborValue(dtTzOffset)
<< QCborKnownTags::DateTimeString << QCborValue(dtTzOffset.toString(Qt::ISODateWithMs)) << v;
QTest::newRow("Url:Empty") << QCborValue(QUrl())
<< QCborValue(QCborKnownTags::Url, QString());
<< QCborKnownTags::Url << QCborValue(QString()) << v;
QTest::newRow("Url:Authority") << QCborValue(QUrl("https://example.com"))
<< QCborValue(QCborKnownTags::Url, "https://example.com");
<< QCborKnownTags::Url << QCborValue("https://example.com") << v;
QTest::newRow("Url:Path") << QCborValue(QUrl("file:///tmp/none"))
<< QCborValue(QCborKnownTags::Url, "file:///tmp/none");
<< QCborKnownTags::Url << QCborValue("file:///tmp/none") << v;
QTest::newRow("Url:QueryFragment") << QCborValue(QUrl("whatever:?a=b&c=d#e"))
<< QCborValue(QCborKnownTags::Url, "whatever:?a=b&c=d#e");
<< QCborKnownTags::Url << QCborValue("whatever:?a=b&c=d#e") << v;
QTest::newRow("Regex:Empty") << QCborValue(QRegularExpression())
<< QCborValue(QCborKnownTags::RegularExpression, QString());
<< QCborKnownTags::RegularExpression << QCborValue(QString()) << v;
QTest::newRow("Regex") << QCborValue(QRegularExpression("^.*$"))
<< QCborValue(QCborKnownTags::RegularExpression, QString("^.*$"));
<< QCborKnownTags::RegularExpression << QCborValue(QString("^.*$")) << v;
QTest::newRow("Uuid") << QCborValue(uuid)
<< QCborValue(QCborKnownTags::Uuid, uuid.toRfc4122());
<< QCborKnownTags::Uuid << QCborValue(uuid.toRfc4122()) << v;
// correcting extended types
QDateTime dtNoMsecs = dt.fromSecsSinceEpoch(dt.toSecsSinceEpoch(), Qt::UTC);
QUrl url("https://example.com/\xc2\xa9 ");
QTest::newRow("UnixTime_t:Integer") << QCborValue(dtNoMsecs) << QCborKnownTags::UnixTime_t
<< QCborValue(dtNoMsecs.toSecsSinceEpoch())
<< QCborValue(dtNoMsecs.toString(Qt::ISODateWithMs));
QTest::newRow("UnixTime_t:Double") << QCborValue(dt) << QCborKnownTags::UnixTime_t
<< QCborValue(dt.toMSecsSinceEpoch() / 1000.)
<< QCborValue(dt.toString(Qt::ISODateWithMs));
QTest::newRow("DateTime::JustDate") << QCborValue(QDateTime({2018, 1, 1}, {}))
<< QCborKnownTags::DateTimeString
<< QCborValue("2018-01-01") << QCborValue("2018-01-01T00:00:00.000");
QTest::newRow("DateTime::TzOffset") << QCborValue(QDateTime({2018, 1, 1}, {9, 0, 0}, Qt::UTC))
<< QCborKnownTags::DateTimeString
<< QCborValue("2018-01-01T09:00:00.000+00:00")
<< QCborValue("2018-01-01T09:00:00.000Z");
QTest::newRow("Url:NotNormalized") << QCborValue(url) << QCborKnownTags::Url
<< QCborValue("HTTPS://EXAMPLE.COM/%c2%a9%20")
<< QCborValue(url.toString());
QTest::newRow("Uuid:Zero") << QCborValue(QUuid()) << QCborKnownTags::Uuid
<< QCborValue(QByteArray())
<< QCborValue(QByteArray(sizeof(QUuid), 0));
QTest::newRow("Uuid:TooShort") << QCborValue(QUuid(0x12345678, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
<< QCborKnownTags::Uuid
<< QCborValue(raw("\x12\x34\x56\x78"))
<< QCborValue(raw("\x12\x34\x56\x78" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0"));
QTest::newRow("Uuid:TooLong") << QCborValue(uuid) << QCborKnownTags::Uuid
<< QCborValue(uuid.toRfc4122() + "\1\2\3\4") << QCborValue(uuid.toRfc4122());
}
void tst_QCborValue::extendedTypes()
{
QFETCH(QCborValue, extended);
QFETCH(QCborValue, tagged);
QFETCH(QCborKnownTags, tag);
QFETCH(QCborValue, taggedValue);
QFETCH(QCborValue, correctedTaggedValue);
if (correctedTaggedValue.isInvalid())
correctedTaggedValue = taggedValue;
QCborValue tagged(tag, taggedValue);
QVERIFY(extended.isTag());
QVERIFY(tagged.isTag());
QCOMPARE(tagged.taggedValue(), correctedTaggedValue);
QVERIFY(extended == tagged);
QVERIFY(tagged == extended);
@ -1224,8 +1268,11 @@ void tst_QCborValue::sorting()
QCborValue va1(QCborValue::Array), va2(QCborArray{1}), va3(QCborArray{0, 0});
QCborValue vm1(QCborValue::Map), vm2(QCborMap{{1, 0}}), vm3(QCborMap{{0, 0}, {1, 0}});
QCborValue vdt1(QDateTime::fromMSecsSinceEpoch(0, Qt::UTC)), vdt2(QDateTime::currentDateTimeUtc());
QCborValue vtagged1(QCborKnownTags::UnixTime_t, 0), vtagged2(QCborKnownTags::UnixTime_t, 0.0),
vtagged3(QCborKnownTags::Signature, 0), vtagged4(QCborTag(-2), 0), vtagged5(QCborTag(-1), 0);
QCborValue vtagged1(QCborKnownTags::PositiveBignum, QByteArray()),
vtagged2(QCborKnownTags::PositiveBignum, 0.0), // bignums are supposed to have byte arrays...
vtagged3(QCborKnownTags::Signature, 0),
vtagged4(QCborTag(-2), 0),
vtagged5(QCborTag(-1), 0);
QCborValue vurl1(QUrl("https://example.net")), vurl2(QUrl("https://example.com/"));
QCborValue vuuid1{QUuid()}, vuuid2(QUuid::createUuid());
QCborValue vsimple1(QCborSimpleType(1)), vsimple32(QCborSimpleType(32)), vsimple255(QCborSimpleType(255));