Revision control
++ // Copyright (C) 2016 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:critical reason:data-parser
++
++ #include "qndefmessage.h"
++ #include "qndefrecord_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ QT_IMPL_METATYPE_EXTERN(QNdefMessage)
++
++ /*!
++ \class QNdefMessage
++ \brief The QNdefMessage class provides an NFC NDEF message.
++
++ \ingroup connectivity-nfc
++ \inmodule QtNfc
++ \since Qt 5.2
++
++ A QNdefMessage is a collection of 0 or more QNdefRecords. QNdefMessage inherits from
++ QList<QNdefRecord> and therefore the standard QList functions can be used to manipulate the
++ NDEF records in the message.
++
++ NDEF messages can be parsed from a byte array conforming to the NFC Data Exchange Format
++ technical specification by using the fromByteArray() static function. Conversely QNdefMessages
++ can be converted into a byte array with the toByteArray() function.
++ */
++
++ /*!
++ \fn QNdefMessage::QNdefMessage()
++
++ Constructs a new empty NDEF message.
++ */
++
++ /*!
++ \fn QNdefMessage::QNdefMessage(const QNdefRecord &record)
++
++ Constructs a new NDEF message containing a single record \a record.
++ */
++
++ /*!
++ \fn QNdefMessage::QNdefMessage(const QNdefMessage &message)
++
++ Constructs a new NDEF message that is a copy of \a message.
++ */
++
++ /*!
++ \fn QNdefMessage::QNdefMessage(const QList<QNdefRecord> &records)
++
++ Constructs a new NDEF message that contains all of the records in \a records.
++ */
++
++ /*!
++ Returns an NDEF message parsed from the contents of \a message.
++
++ The \a message parameter is interpreted as the raw message format defined in the NFC Data
++ Exchange Format technical specification.
++
++ If a parse error occurs an empty NDEF message is returned.
++ */
++ QNdefMessage QNdefMessage::fromByteArray(const QByteArray &message)
++ {
++ QNdefMessage result;
++
++ bool seenMessageBegin = false;
++ bool seenMessageEnd = false;
++
++ // We need a special flag to detect chunking, because any payload of a chunk
++ // (including the first and the last) can be empty, so using
++ // partialChunk.size() is not enough.
++ bool chunkStarted = false;
++ QByteArray partialChunk;
++ QNdefRecord record;
++
++ qsizetype idx = 0;
++ while (idx < message.size()) {
++ quint8 flags = message.at(idx);
++
++ const bool messageBegin = flags & 0x80;
++ const bool messageEnd = flags & 0x40;
++
++ const bool cf = flags & 0x20;
++ const bool sr = flags & 0x10;
++ const bool il = flags & 0x08;
++ const quint8 typeNameFormat = flags & 0x07;
++
++ if (messageBegin && seenMessageBegin) {
++ qWarning("Got message begin but already parsed some records");
++ return QNdefMessage();
++ } else if (!messageBegin && !seenMessageBegin) {
++ qWarning("Haven't got message begin yet");
++ return QNdefMessage();
++ } else if (messageBegin && !seenMessageBegin) {
++ seenMessageBegin = true;
++ }
++ if (messageEnd && seenMessageEnd) {
++ qWarning("Got message end but already parsed final record");
++ return QNdefMessage();
++ } else if (messageEnd && !seenMessageEnd) {
++ seenMessageEnd = true;
++ }
++ // TNF must be 0x06 even for the last chunk, when cf == 0.
++ if ((typeNameFormat != 0x06) && chunkStarted) {
++ qWarning("Partial chunk not empty, but TNF not 0x06 as expected");
++ return QNdefMessage();
++ }
++ // TNF cannot be 0x06 if the chunking was not started
++ if ((typeNameFormat == 0x06) && !chunkStarted) {
++ qWarning("Unexpected TNF=0x06 when chunking was not started");
++ return QNdefMessage();
++ }
++
++ int headerLength = 1;
++ headerLength += (sr) ? 1 : 4;
++ headerLength += (il) ? 1 : 0;
++
++ if (idx + headerLength >= message.size()) {
++ qWarning("Unexpected end of message");
++ return QNdefMessage();
++ }
++
++ const quint8 typeLength = message.at(++idx);
++
++ if ((typeNameFormat == 0x06) && (typeLength != 0)) {
++ qWarning("Invalid chunked data, TYPE_LENGTH != 0");
++ return QNdefMessage();
++ }
++
++ quint32 payloadLength;
++ if (sr)
++ payloadLength = quint8(message.at(++idx));
++ else {
++ payloadLength = quint8(message.at(++idx)) << 24;
++ payloadLength |= quint8(message.at(++idx)) << 16;
++ payloadLength |= quint8(message.at(++idx)) << 8;
++ payloadLength |= quint8(message.at(++idx)) << 0;
++ }
++
++ quint8 idLength;
++ if (il)
++ idLength = message.at(++idx);
++ else
++ idLength = 0;
++
++ // On 32-bit systems this can overflow
++ const qsizetype convertedPayloadLength = static_cast<qsizetype>(payloadLength);
++ const qsizetype contentLength = convertedPayloadLength + typeLength + idLength;
++
++ // On a 32 bit platform the payload can theoretically exceed the max.
++ // size of a QByteArray. This will never happen in practice with correct
++ // data because there are no NFC tags that can store such data sizes,
++ // but still can be possible if the data is corrupted.
++ if ((contentLength < 0) || (convertedPayloadLength < 0)
++ || ((std::numeric_limits<qsizetype>::max() - idx) < contentLength)) {
++ qWarning("Payload can't fit into QByteArray");
++ return QNdefMessage();
++ }
++
++ if (idx + contentLength >= message.size()) {
++ qWarning("Unexpected end of message");
++ return QNdefMessage();
++ }
++
++ if ((typeNameFormat == 0x06) && il) {
++ qWarning("Invalid chunked data, IL != 0");
++ return QNdefMessage();
++ }
++
++ if (typeNameFormat != 0x06)
++ record.setTypeNameFormat(QNdefRecord::TypeNameFormat(typeNameFormat));
++
++ if (typeLength > 0) {
++ QByteArray type(&message.constData()[++idx], typeLength);
++ record.setType(type);
++ idx += typeLength - 1;
++ }
++
++ if (idLength > 0) {
++ QByteArray id(&message.constData()[++idx], idLength);
++ record.setId(id);
++ idx += idLength - 1;
++ }
++
++ {
++ QByteArray payload;
++ if (payloadLength > 0) {
++ payload = QByteArray(&message.constData()[++idx], payloadLength);
++ idx += payloadLength - 1;
++ }
++
++ if (cf) {
++ // chunked payload, except last
++ chunkStarted = true;
++ partialChunk.append(payload);
++ } else if (typeNameFormat == 0x06) {
++ // last chunk of chunked payload
++ record.setPayload(partialChunk + payload);
++ partialChunk.clear();
++ chunkStarted = false;
++ } else {
++ // non-chunked payload
++ record.setPayload(payload);
++ }
++ }
++
++ if (!cf) {
++ result.append(record);
++ record = QNdefRecord();
++ }
++
++ if (!cf && seenMessageEnd)
++ break;
++
++ // move to start of next record
++ ++idx;
++ }
++
++ if (!seenMessageBegin || !seenMessageEnd) {
++ qWarning("Malformed NDEF Message, missing begin or end");
++ return QNdefMessage();
++ }
++
++ return result;
++ }
++
++ /*!
++ \fn QNdefMessage &QNdefMessage::operator=(const QNdefMessage &other)
++ \overload
++ \since 6.2
++
++ Copy assignment operator from QList<QNdefRecord>. Assigns the
++ \a other list of NDEF records to this NDEF record list.
++
++ After the operation, \a other and \c *this will be equal.
++ */
++
++ /*!
++ \fn QNdefMessage &QNdefMessage::operator=(QNdefMessage &&other)
++ \overload
++ \since 6.2
++
++ Move assignment operator from QList<QNdefRecord>. Moves the
++ \a other list of NDEF records to this NDEF record list.
++
++ After the operation, \a other will be empty.
++ */
++
++ /*!
++ Returns \c true if this NDEF message is equivalent to \a other; otherwise
++ returns \c false.
++
++ An empty message (i.e. isEmpty() returns \c true) is equivalent to a NDEF
++ message containing a single record of type \l QNdefRecord::Empty.
++ */
++ bool QNdefMessage::operator==(const QNdefMessage &other) const
++ {
++ // both records are empty
++ if (isEmpty() && other.isEmpty())
++ return true;
++
++ // compare empty to really empty
++ if (isEmpty() && other.size() == 1 && other.first().typeNameFormat() == QNdefRecord::Empty)
++ return true;
++ if (other.isEmpty() && size() == 1 && first().typeNameFormat() == QNdefRecord::Empty)
++ return true;
++
++ if (size() != other.size())
++ return false;
++
++ for (qsizetype i = 0; i < size(); ++i) {
++ if (at(i) != other.at(i))
++ return false;
++ }
++
++ return true;
++ }
++
++ /*!
++ Returns the NDEF message as a byte array.
++
++ The return value of this function conforms to the format defined in the NFC Data Exchange
++ Format technical specification.
++ */
++ QByteArray QNdefMessage::toByteArray() const
++ {
++ // An empty message is treated as a message containing a single empty record.
++ if (isEmpty())
++ return QNdefMessage(QNdefRecord()).toByteArray();
++
++ QByteArray m;
++
++ for (qsizetype i = 0; i < size(); ++i) {
++ const QNdefRecord &record = at(i);
++
++ quint8 flags = record.typeNameFormat();
++
++ if (i == 0)
++ flags |= 0x80;
++ if (i == size() - 1)
++ flags |= 0x40;
++
++ // cf (chunked records) not supported yet
++
++ if (record.payload().size() < 255)
++ flags |= 0x10;
++
++ if (!record.id().isEmpty())
++ flags |= 0x08;
++
++ m.append(flags);
++ m.append(record.type().size());
++
++ if (flags & 0x10) {
++ m.append(quint8(record.payload().size()));
++ } else {
++ quint32 length = record.payload().size();
++ m.append(length >> 24);
++ m.append(length >> 16);
++ m.append(length >> 8);
++ m.append(length & 0x000000ff);
++ }
++
++ if (flags & 0x08)
++ m.append(record.id().size());
++
++ if (!record.type().isEmpty())
++ m.append(record.type());
++
++ if (!record.id().isEmpty())
++ m.append(record.id());
++
++ if (!record.payload().isEmpty())
++ m.append(record.payload());
++ }
++
++ return m;
++ }
++
++ QT_END_NAMESPACE