QCborStreamReader: add toString() and toByteArray()
They've been a long time coming. I had them in TinyCBOR before the chunked reading; in fact, I added the chunked reading specifically for Qt's CBOR support. [ChangeLog][QtCore][QCborStreamReader] Added toString() and toByteArray(), which read a full string whether it is chunked or not. They also guard against attempting to allocate more memory than the underlying stream could provide. Change-Id: Icfe44ecf285a480fafe4fffd174c5815f153d5b5 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
8e8815b688
commit
8af346c1f6
@ -630,6 +630,7 @@ public:
|
||||
};
|
||||
|
||||
static QCborStreamReader::StringResultCode appendStringChunk(QCborStreamReader &reader, QByteArray *data);
|
||||
bool readFullString(ReadStringChunk params);
|
||||
QCborStreamReader::StringResult<qsizetype> readStringChunk(ReadStringChunk params);
|
||||
qsizetype readStringChunk_byte(ReadStringChunk params, qsizetype len);
|
||||
qsizetype readStringChunk_unicode(ReadStringChunk params, qsizetype utf8len);
|
||||
@ -1295,11 +1296,15 @@ bool QCborStreamReader::leaveContainer()
|
||||
|
||||
\snippet code/src_corelib_serialization_qcborstream.cpp 27
|
||||
|
||||
The toString() function implements the above loop and some extra checks.
|
||||
|
||||
//! [string-no-type-conversions]
|
||||
This function does not perform any type conversions, including from integers
|
||||
or from byte arrays. Therefore, it may only be called if isString() returned
|
||||
true; calling it in any other condition is an error.
|
||||
//! [string-no-type-conversions]
|
||||
|
||||
\sa readByteArray(), isString(), readStringChunk()
|
||||
\sa toString(), readByteArray(), isString(), readStringChunk()
|
||||
*/
|
||||
QCborStreamReader::StringResult<QString> QCborStreamReader::_readString_helper()
|
||||
{
|
||||
@ -1327,11 +1332,15 @@ QCborStreamReader::StringResult<QString> QCborStreamReader::_readString_helper()
|
||||
|
||||
\snippet code/src_corelib_serialization_qcborstream.cpp 28
|
||||
|
||||
The toByteArray() function implements the above loop and some extra checks.
|
||||
|
||||
//! [bytearray-no-type-conversions]
|
||||
This function does not perform any type conversions, including from integers
|
||||
or from strings. Therefore, it may only be called if isByteArray() is true;
|
||||
calling it in any other condition is an error.
|
||||
//! [bytearray-no-type-conversions]
|
||||
|
||||
\sa readString(), isByteArray(), readStringChunk()
|
||||
\sa toByteArray(), readString(), isByteArray(), readStringChunk()
|
||||
*/
|
||||
QCborStreamReader::StringResult<QByteArray> QCborStreamReader::_readByteArray_helper()
|
||||
{
|
||||
@ -1380,6 +1389,108 @@ qsizetype QCborStreamReader::_currentStringChunkSize() const
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool QCborStreamReaderPrivate::readFullString(ReadStringChunk params)
|
||||
{
|
||||
auto r = readStringChunk(params);
|
||||
while (r.status == QCborStreamReader::Ok) {
|
||||
// keep appending
|
||||
r = readStringChunk(params);
|
||||
}
|
||||
|
||||
bool ok = r.status == QCborStreamReader::EndOfString;
|
||||
Q_ASSERT(ok == !lastError);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QCborStreamReader::toString()
|
||||
\since 6.7
|
||||
|
||||
Decodes the current text string and returns it. If the string is chunked,
|
||||
this function will iterate over all chunks and concatenate them. If an
|
||||
error happens, this function returns a default-constructed QString(), but
|
||||
that may not be distinguishable from certain empty text strings. Instead,
|
||||
check lastError() to determine if an error has happened.
|
||||
|
||||
\include qcborstreamreader.cpp string-no-type-conversions
|
||||
|
||||
//! [note-not-restartable]
|
||||
\note This function cannot be resumed. That is, this function should not
|
||||
be used in contexts where the CBOR data may still be received, for example
|
||||
from a socket or pipe. It should only be used when the full data has
|
||||
already been received and is available in the input QByteArray or
|
||||
QIODevice.
|
||||
//! [note-not-restartable]
|
||||
|
||||
\sa readString(), readStringChunk(), isString(), toByteArray()
|
||||
*/
|
||||
/*!
|
||||
\fn QCborStreamReader::toString(QString &dst)
|
||||
\overload
|
||||
\since 6.7
|
||||
|
||||
Decodes the current text string and appends to \a dst. If the string is
|
||||
chunked, this function will iterate over all chunks and concatenate them.
|
||||
If an error happens during decoding, other chunks that could be decoded
|
||||
successfully may have been written to \a dst nonetheless. Returns \c true
|
||||
if the decoding happened without errors, \c false otherwise.
|
||||
|
||||
\include qcborstreamreader.cpp string-no-type-conversions
|
||||
|
||||
\include qcborstreamreader.cpp note-not-restartable
|
||||
|
||||
\sa readString(), readStringChunk(), isString(), toByteArray()
|
||||
*/
|
||||
bool QCborStreamReader::_toString_helper(QString &dst)
|
||||
{
|
||||
bool ok = d->readFullString(&dst);
|
||||
if (ok)
|
||||
preparse();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QCborStreamReader::toByteArray()
|
||||
\since 6.7
|
||||
|
||||
Decodes the current byte string and returns it. If the string is chunked,
|
||||
this function will iterate over all chunks and concatenate them. If an
|
||||
error happens, this function returns a default-constructed QByteArray(),
|
||||
but that may not be distinguishable from certain empty byte strings.
|
||||
Instead, check lastError() to determine if an error has happened.
|
||||
|
||||
\include qcborstreamreader.cpp bytearray-no-type-conversions
|
||||
|
||||
\include qcborstreamreader.cpp note-not-restartable
|
||||
|
||||
\sa readByteArray(), readStringChunk(), isByteArray(), toString()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QCborStreamReader::toByteArray(QByteArray &dst)
|
||||
\overload
|
||||
\since 6.7
|
||||
|
||||
Decodes the current byte string and appends to \a dst. If the string is
|
||||
chunked, this function will iterate over all chunks and concatenate them.
|
||||
If an error happens during decoding, other chunks that could be decoded
|
||||
successfully may have been written to \a dst nonetheless. Returns \c true
|
||||
if the decoding happened without errors, \c false otherwise.
|
||||
|
||||
\include qcborstreamreader.cpp bytearray-no-type-conversions
|
||||
|
||||
\include qcborstreamreader.cpp note-not-restartable
|
||||
|
||||
\sa readByteArray(), readStringChunk(), isByteArray(), toString()
|
||||
*/
|
||||
bool QCborStreamReader::_toByteArray_helper(QByteArray &dst)
|
||||
{
|
||||
bool ok = d->readFullString(&dst);
|
||||
if (ok)
|
||||
preparse();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*!
|
||||
Reads the current string chunk into the buffer pointed to by \a ptr, whose
|
||||
size is \a maxlen. This function returns a \l StringResult object, with the
|
||||
@ -1452,6 +1563,12 @@ QCborStreamReaderPrivate::readStringChunk(ReadStringChunk params)
|
||||
// qt_cbor_decoder_transfer_string() enforces that
|
||||
// QIODevice::bytesAvailable() be bigger than the amount we're about to
|
||||
// read.
|
||||
//
|
||||
// This is an important security gate: if the CBOR stream is corrupt or
|
||||
// malicious, and has an impossibly large string size, we only go past it
|
||||
// if the transfer to the destination buffer will succeed (modulo QIODevice
|
||||
// I/O failures).
|
||||
|
||||
#if 1
|
||||
// Using internal TinyCBOR API!
|
||||
err = _cbor_value_get_string_chunk(¤tElement, &content, &len, ¤tElement);
|
||||
|
@ -120,6 +120,8 @@ public:
|
||||
bool enterContainer() { Q_ASSERT(isContainer()); return _enterContainer_helper(); }
|
||||
bool leaveContainer();
|
||||
|
||||
bool toString(QString &dst) { Q_ASSERT(isString()); return _toString_helper(dst); }
|
||||
bool toByteArray(QByteArray &dst) { Q_ASSERT(isByteArray()); return _toByteArray_helper(dst); }
|
||||
StringResult<QString> readString() { Q_ASSERT(isString()); return _readString_helper(); }
|
||||
StringResult<QByteArray> readByteArray(){ Q_ASSERT(isByteArray()); return _readByteArray_helper(); }
|
||||
qsizetype currentStringChunkSize() const{ Q_ASSERT(isString() || isByteArray()); return _currentStringChunkSize(); }
|
||||
@ -142,6 +144,20 @@ public:
|
||||
return -v - 1;
|
||||
return v;
|
||||
}
|
||||
QString toString()
|
||||
{
|
||||
QString dst;
|
||||
if (!toString(dst))
|
||||
dst.clear();
|
||||
return dst;
|
||||
}
|
||||
QByteArray toByteArray()
|
||||
{
|
||||
QByteArray dst;
|
||||
if (!toByteArray(dst))
|
||||
dst.clear();
|
||||
return dst;
|
||||
}
|
||||
|
||||
private:
|
||||
void preparse();
|
||||
@ -149,6 +165,8 @@ private:
|
||||
StringResult<QString> _readString_helper();
|
||||
StringResult<QByteArray> _readByteArray_helper();
|
||||
qsizetype _currentStringChunkSize() const;
|
||||
bool _toString_helper(QString &);
|
||||
bool _toByteArray_helper(QByteArray &);
|
||||
|
||||
template <typename FP> FP _toFloatingPoint() const noexcept
|
||||
{
|
||||
|
@ -657,6 +657,7 @@ void tst_QCborStreamReader::strings()
|
||||
QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length()));
|
||||
|
||||
int chunks = 0;
|
||||
QByteArray fullString;
|
||||
forever {
|
||||
QCborStreamReader::StringResult<QByteArray> controlData;
|
||||
if (reader.isString()) {
|
||||
@ -667,6 +668,7 @@ void tst_QCborStreamReader::strings()
|
||||
controlData = controlReader.readByteArray();
|
||||
}
|
||||
QVERIFY(controlData.status != QCborStreamReader::Error);
|
||||
fullString += controlData.data;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// this call must work several times with the same result
|
||||
@ -689,6 +691,22 @@ void tst_QCborStreamReader::strings()
|
||||
|
||||
if (!isChunked)
|
||||
QCOMPARE(chunks, 1);
|
||||
|
||||
// Now re-do and compare with toString() and toByteArray(), against
|
||||
// the control data we calculated above
|
||||
reader.reset();
|
||||
QVERIFY(reader.isString() || reader.isByteArray());
|
||||
if (reader.isByteArray()) {
|
||||
QByteArray prefix("some prefix");
|
||||
QByteArray ba = prefix;
|
||||
QVERIFY(reader.toByteArray(ba));
|
||||
QCOMPARE(ba, prefix + fullString);
|
||||
} else {
|
||||
QString prefix("some prefix");
|
||||
QString str = prefix;
|
||||
QVERIFY(reader.toString(str));
|
||||
QCOMPARE(str, prefix + QString::fromUtf8(fullString));
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::tags_data()
|
||||
@ -908,6 +926,29 @@ void tst_QCborStreamReader::validation()
|
||||
reader.reset();
|
||||
QVERIFY(!reader.next());
|
||||
QCOMPARE(reader.lastError(), error);
|
||||
|
||||
// check toString() and toByteArray() too
|
||||
if (reader.isString() || reader.isByteArray()) {
|
||||
reader.reset();
|
||||
if (reader.isString()) {
|
||||
QString prefix = "some prefix";
|
||||
QString str = prefix;
|
||||
QVERIFY(!reader.toString(str));
|
||||
QVERIFY(str.startsWith(prefix)); // but may have decoded some
|
||||
} else if (reader.isByteArray()) {
|
||||
QByteArray prefix = "some prefix";
|
||||
QByteArray ba = prefix;
|
||||
QVERIFY(!reader.toByteArray(ba));
|
||||
QVERIFY(ba.startsWith(prefix)); // but may have decoded some
|
||||
}
|
||||
QCOMPARE(reader.lastError(), error);
|
||||
|
||||
reader.reset();
|
||||
if (reader.isString())
|
||||
QVERIFY(reader.toString().isNull());
|
||||
else
|
||||
QVERIFY(reader.toByteArray().isNull());
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QCborStreamReader::hugeDeviceValidation_data()
|
||||
|
Loading…
x
Reference in New Issue
Block a user