qdataurl: make parsing the content-type more robust

Don't assume `charset` is the first parameter. The parameters
(attribute=value pairs) are delemited by a `;`. The order of the
parameters isn't specified, (except for `;base64` which is the last
one).

Add more tests. Add a test for image/png (.png file copied from
src/widgets/styles/images/arrow-down-16.png).

Change-Id: Ie3e45c607c093695d0c180e9a9783b2b02d7ef70
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2025-04-26 16:51:30 +03:00
parent d4960f380e
commit 29a00e959b
4 changed files with 62 additions and 11 deletions

View File

@ -17,6 +17,15 @@ using namespace Qt::Literals;
*/
Q_CORE_EXPORT bool qDecodeDataUrl(const QUrl &uri, QString &mimeType, QByteArray &payload)
{
/* https://www.rfc-editor.org/rfc/rfc2397.html
data:[<mediatype>][;base64],<data>
dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype := [ type "/" subtype ] *( ";" parameter )
data := *urlchar
parameter := attribute "=" value
*/
if (uri.scheme() != "data"_L1 || !uri.host().isEmpty())
return false;
@ -48,20 +57,36 @@ Q_CORE_EXPORT bool qDecodeDataUrl(const QUrl &uri, QString &mimeType, QByteArray
data.chop(base64.size());
}
QLatin1StringView textPlain;
QLatin1StringView mime;
QLatin1StringView charsetParam;
constexpr auto charset = "charset"_L1;
if (data.startsWith(charset, Qt::CaseInsensitive)) {
QLatin1StringView copy = data.sliced(charset.size());
while (copy.startsWith(u' '))
copy.slice(1);
if (copy.startsWith(u'='))
textPlain = "text/plain;"_L1;
bool first = true;
for (auto part : qTokenize(data, u';', Qt::SkipEmptyParts)) {
part = part.trimmed();
if (first) {
if (part.contains(u'/'))
mime = part;
first = false;
}
// Minimal changes, e.g. if it's "charset=;" or "charset;" without
// an encoding, leave it as-is
if (part.startsWith(charset, Qt::CaseInsensitive))
charsetParam = part;
if (!mime.isEmpty() && !charsetParam.isEmpty())
break;
}
if (!data.isEmpty())
mimeType = textPlain + data.trimmed();
if (mime.isEmpty()) {
mime = "text/plain"_L1;
if (charsetParam.isEmpty())
charsetParam = "charset=US-ASCII"_L1;
}
if (!charsetParam.isEmpty())
mimeType = mime + u';' + charsetParam;
else
mimeType = QStringLiteral("text/plain;charset=US-ASCII");
mimeType = mime;
return true;
}

View File

@ -16,4 +16,5 @@ qt_internal_add_test(tst_qdataurl
tst_qdataurl.cpp
LIBRARIES
Qt::CorePrivate
TESTDATA "arrow-down-16.png"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View File

@ -32,10 +32,14 @@ void tst_QDataUrl::decode_data()
row("malformed-host2", "data://text/plain;charset=ISO-8859-1", false);
row("malformed-host3", "data://test.com/,", false);
row("malformed-no-comma", "data:text/plain", false);
constexpr auto defaultMimeType = "text/plain;charset=US-ASCII"_L1;
row("emptyData-default-mimetype", "data:,", true,
"text/plain;charset=US-ASCII"_L1, "");
row("emptyData-only-charset", "data:charset=ISO-8859-1,", true,
"text/plain;charset=ISO-8859-1"_L1, "");
row("alreadyPercentageEncoded", "data:text/plain,%E2%88%9A", true,
"text/plain"_L1, QByteArray::fromPercentEncoding("%E2%88%9A"));
row("everythingIsCaseInsensitive", "Data:texT/PlaiN;charSet=iSo-8859-1;Base64,SGVsbG8=", true,
@ -43,7 +47,28 @@ void tst_QDataUrl::decode_data()
row("spacesAroundCharset", "data:%20charset%20%20=%20UTF-8,Hello", true,
"text/plain;charset = UTF-8"_L1, QByteArrayLiteral("Hello"));
row("prematureCharsetEnd", "data:charset,", true,
"charset", ""); // nonsense result, but don't crash
"text/plain;charset"_L1, ""); // nonsense result, but don't crash
row("prematureCharsetEnd-no-encoding", "data:charset=,", true,
"text/plain;charset="_L1, ""); // nonsense result, but don't crash
row("charset-value-as-quoted-string", "data:charset=%22UTF-8%22,Hello", true,
"text/plain;charset=\"UTF-8\""_L1, "Hello"_ba);
row("extraparameter-before-charset", "data:;extraparameter=foo;charset=ISO-8859-1,Hello", true,
"text/plain;charset=ISO-8859-1", "Hello");
row("extraparameter-after-charset", "data:charset=ISO-8859-1;extraparameter=foo,Hello", true,
"text/plain;charset=ISO-8859-1", "Hello");
row("content-type-parsing-slash-in-mimetype",
"data:charset=UTF-8;alternate=\"application/octet-stream\";bar=baz,", true,
"text/plain;charset=UTF-8", "");
row("not-real-charset", "data:incharsetter=true,", true, defaultMimeType, "");
QString path = QFINDTESTDATA("arrow-down-16.png");
QFile img(path);
QVERIFY(img.open(QFile::ReadOnly));
QByteArray imageData = img.readAll();
QByteArray base64 = imageData.toBase64();
row("image-png", "data:image/png;base64," + base64, true,
"image/png", imageData);
}
void tst_QDataUrl::decode()