diff --git a/src/corelib/serialization/qcborstreamreader.cpp b/src/corelib/serialization/qcborstreamreader.cpp index 47d1f8e014f..041d2aa27e3 100644 --- a/src/corelib/serialization/qcborstreamreader.cpp +++ b/src/corelib/serialization/qcborstreamreader.cpp @@ -630,6 +630,7 @@ public: }; static QCborStreamReader::StringResultCode appendStringChunk(QCborStreamReader &reader, QByteArray *data); + bool readFullString(ReadStringChunk params); QCborStreamReader::StringResult 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 QCborStreamReader::_readString_helper() { @@ -1327,11 +1332,15 @@ QCborStreamReader::StringResult 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 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); diff --git a/src/corelib/serialization/qcborstreamreader.h b/src/corelib/serialization/qcborstreamreader.h index 5a870d17a30..5e0b293863d 100644 --- a/src/corelib/serialization/qcborstreamreader.h +++ b/src/corelib/serialization/qcborstreamreader.h @@ -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 readString() { Q_ASSERT(isString()); return _readString_helper(); } StringResult 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 _readString_helper(); StringResult _readByteArray_helper(); qsizetype _currentStringChunkSize() const; + bool _toString_helper(QString &); + bool _toByteArray_helper(QByteArray &); template FP _toFloatingPoint() const noexcept { diff --git a/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp index 96d0f77c327..477894fb5cc 100644 --- a/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp +++ b/tests/auto/corelib/serialization/qcborstreamreader/tst_qcborstreamreader.cpp @@ -657,6 +657,7 @@ void tst_QCborStreamReader::strings() QCOMPARE(reader.currentStringChunkSize(), qsizetype(reader.length())); int chunks = 0; + QByteArray fullString; forever { QCborStreamReader::StringResult 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()