diff --git a/src/corelib/text/qstringconverter.cpp b/src/corelib/text/qstringconverter.cpp index 4972bcde4f..9a3d92dbaa 100644 --- a/src/corelib/text/qstringconverter.cpp +++ b/src/corelib/text/qstringconverter.cpp @@ -1491,6 +1491,46 @@ std::optional QStringConverter::encodingForName(cons return std::nullopt; } +std::optional QStringConverter::encodingForData(const char *buf, qsizetype arraySize, char16_t expectedFirstCharacter) +{ + if (arraySize > 3) { + uint uc = qFromUnaligned(buf); + if (uc == qToBigEndian(uint(QChar::ByteOrderMark))) + return QStringConverter::Utf32BE; + if (uc == qToLittleEndian(uint(QChar::ByteOrderMark))) + return QStringConverter::Utf32LE; + if (expectedFirstCharacter) { + // catch also anything starting with the expected character + if (qToLittleEndian(uc) == expectedFirstCharacter) + return QStringConverter::Utf32LE; + else if (qToBigEndian(uc) == expectedFirstCharacter) + return QStringConverter::Utf32BE; + } + } + + if (arraySize > 2) { + static const char utf8bom[] = "\xef\xbb\xbf"; + if (memcmp(buf, utf8bom, sizeof(utf8bom) - 1) == 0) + return QStringConverter::Utf8; + } + + if (arraySize > 1) { + ushort uc = qFromUnaligned(buf); + if (uc == qToBigEndian(ushort(QChar::ByteOrderMark))) + return QStringConverter::Utf16BE; + if (uc == qToLittleEndian(ushort(QChar::ByteOrderMark))) + return QStringConverter::Utf16LE; + if (expectedFirstCharacter) { + // catch also anything starting with the expected character + if (qToLittleEndian(uc) == expectedFirstCharacter) + return QStringConverter::Utf16LE; + else if (qToBigEndian(uc) == expectedFirstCharacter) + return QStringConverter::Utf16BE; + } + } + return std::nullopt; +} + const char *QStringConverter::nameForEncoding(QStringConverter::Encoding e) { return encodingInterfaces[int(e)].name; diff --git a/src/corelib/text/qstringconverter.h b/src/corelib/text/qstringconverter.h index fe401ecc9e..6269ace4ac 100644 --- a/src/corelib/text/qstringconverter.h +++ b/src/corelib/text/qstringconverter.h @@ -167,6 +167,7 @@ public: Q_CORE_EXPORT static std::optional encodingForName(const char *name); Q_CORE_EXPORT static const char *nameForEncoding(Encoding e); + Q_CORE_EXPORT static std::optional encodingForData(const char *buf, qsizetype arraySize, char16_t expectedFirstCharacter = 0); protected: const Interface *iface; diff --git a/tests/auto/corelib/text/qstringconverter/tst_qstringconverter.cpp b/tests/auto/corelib/text/qstringconverter/tst_qstringconverter.cpp index f0e794e910..3f4bbb413f 100644 --- a/tests/auto/corelib/text/qstringconverter/tst_qstringconverter.cpp +++ b/tests/auto/corelib/text/qstringconverter/tst_qstringconverter.cpp @@ -65,6 +65,9 @@ private slots: void nameForEncoding_data(); void nameForEncoding(); + + void encodingForData_data(); + void encodingForData(); }; void tst_QStringConverter::constructByName() @@ -1664,6 +1667,61 @@ void tst_QStringConverter::nameForEncoding() QCOMPARE(n, name); } +void tst_QStringConverter::encodingForData_data() +{ + QTest::addColumn("encoded"); + QTest::addColumn>("encoding"); + + + QTest::newRow("utf8 bom") + << QByteArray("\xef\xbb\xbfhello") + << std::optional(QStringConverter::Utf8); + QTest::newRow("utf8 nobom") + << QByteArray("hello") + << std::optional(); + + QTest::newRow("utf16 bom be") + << QByteArray("\xfe\xff\0h\0e\0l", 8) + << std::optional(QStringConverter::Utf16BE); + QTest::newRow("utf16 bom le") + << QByteArray("\xff\xfeh\0e\0l\0", 8) + << std::optional(QStringConverter::Utf16LE); + QTest::newRow("utf16 nobom be") + << QByteArray("\0<\0e\0l", 6) + << std::optional(QStringConverter::Utf16BE); + QTest::newRow("utf16 nobom le") + << QByteArray("<\0e\0l\0", 6) + << std::optional(QStringConverter::Utf16LE); + QTest::newRow("utf16 nobom no match") + << QByteArray("h\0e\0l\0", 6) + << std::optional(); + + QTest::newRow("utf32 bom be") + << QByteArray("\0\0\xfe\xff\0\0\0h\0\0\0e\0\0\0l", 16) + << std::optional(QStringConverter::Utf32BE); + QTest::newRow("utf32 bom le") + << QByteArray("\xff\xfe\0\0h\0\0\0e\0\0\0l\0\0\0", 16) + << std::optional(QStringConverter::Utf32LE); + QTest::newRow("utf32 nobom be") + << QByteArray("\0\0\0<\0\0\0e\0\0\0l", 12) + << std::optional(QStringConverter::Utf32BE); + QTest::newRow("utf32 nobom") + << QByteArray("<\0\0\0e\0\0\0l\0\0\0", 12) + << std::optional(QStringConverter::Utf32LE); + QTest::newRow("utf32 nobom no match") + << QByteArray("\0\0\0h\0\0\0e\0\0\0l", 12) + << std::optional(); +} + +void tst_QStringConverter::encodingForData() +{ + QFETCH(QByteArray, encoded); + QFETCH(std::optional, encoding); + + auto e = QStringConverter::encodingForData(encoded.constData(), encoded.size(), char16_t('<')); + QCOMPARE(e, encoding); +} + class LoadAndConvert: public QRunnable { public: