From 6b23a3c5e4316c5f889ea5e9bbfd785b78716268 Mon Sep 17 00:00:00 2001 From: Juha Vuolle Date: Fri, 7 Jun 2024 11:46:55 +0300 Subject: [PATCH] Accept QASV for multipart 'name' field And adjust the encoding of 'name' parameter to always use UTF-8 / ASCII. This aligns with how other frameworks behave. Also amended docs to recommend ASCII for 'name'. Found in API review. Pick-to: 6.8 Change-Id: I54d1148bf95dece54b75c76914c49985da05e0b2 Reviewed-by: Marc Mutz --- src/network/access/qformdatabuilder.cpp | 30 +++++++++++++++-- src/network/access/qformdatabuilder.h | 4 +-- .../qformdatabuilder/tst_qformdatabuilder.cpp | 32 +++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/network/access/qformdatabuilder.cpp b/src/network/access/qformdatabuilder.cpp index 2cb5cc1f0e8..986286a1213 100644 --- a/src/network/access/qformdatabuilder.cpp +++ b/src/network/access/qformdatabuilder.cpp @@ -28,17 +28,37 @@ QT_BEGIN_NAMESPACE \sa QHttpPart, QHttpMultiPart, QFormDataBuilder */ +static QByteArray nameToByteArray(QStringView view) +{ + return view.toUtf8(); +} + +static QByteArray nameToByteArray(QLatin1StringView view) +{ + if (!QtPrivate::isAscii(view)) + return view.toString().toUtf8(); // ### optimize + + return QByteArray::fromRawData(view.data(), view.size()); +} + +static QByteArray nameToByteArray(QUtf8StringView view) +{ + return QByteArray::fromRawData(view.data(), view.size()); +} + /*! Constructs a QFormDataPartBuilder object and sets \a name as the name parameter of the form-data. */ -QFormDataPartBuilder::QFormDataPartBuilder(QLatin1StringView name, PrivateConstructor /*unused*/) +QFormDataPartBuilder::QFormDataPartBuilder(QAnyStringView name, PrivateConstructor /*unused*/) { static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); + const auto enc = name.visit([](auto name) { return nameToByteArray(name); }); + m_headerValue += "form-data; name=\""; - for (auto c : name) { + for (auto c : enc) { if (c == '"' || c == '\\') m_headerValue += '\\'; m_headerValue += c; @@ -292,10 +312,14 @@ QFormDataBuilder::~QFormDataBuilder() \a name as the name parameter of the form-data. The returned reference is valid until the next call to this function. + Limiting \a name characters to US-ASCII is + \l {https://datatracker.ietf.org/doc/html/rfc7578#section-5.1.1}{strongly recommended} + for interoperability reasons. + \sa QFormDataPartBuilder, QHttpPart */ -QFormDataPartBuilder &QFormDataBuilder::part(QLatin1StringView name) +QFormDataPartBuilder &QFormDataBuilder::part(QAnyStringView name) { static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); diff --git a/src/network/access/qformdatabuilder.h b/src/network/access/qformdatabuilder.h index 68f9f3742c3..332a5e43166 100644 --- a/src/network/access/qformdatabuilder.h +++ b/src/network/access/qformdatabuilder.h @@ -32,7 +32,7 @@ class QFormDataPartBuilder { struct PrivateConstructor { explicit PrivateConstructor() = default; }; public: - Q_NETWORK_EXPORT explicit QFormDataPartBuilder(QLatin1StringView name, PrivateConstructor); + Q_NETWORK_EXPORT explicit QFormDataPartBuilder(QAnyStringView name, PrivateConstructor); QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept : m_headerValue(std::move(other.m_headerValue)), @@ -109,7 +109,7 @@ public: } Q_NETWORK_EXPORT ~QFormDataBuilder(); - Q_NETWORK_EXPORT QFormDataPartBuilder &part(QLatin1StringView name); + Q_NETWORK_EXPORT QFormDataPartBuilder &part(QAnyStringView name); Q_NETWORK_EXPORT std::unique_ptr buildMultiPart(); private: std::vector m_parts; diff --git a/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp index 2dd86e7be58..154edc806c1 100644 --- a/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp +++ b/tests/auto/network/access/qformdatabuilder/tst_qformdatabuilder.cpp @@ -29,6 +29,9 @@ private Q_SLOTS: void specifyMimeType_data(); void specifyMimeType(); + + void picksUtf8NameEncodingIfAsciiDoesNotSuffice_data(); + void picksUtf8NameEncodingIfAsciiDoesNotSuffice(); }; void tst_QFormDataBuilder::generateQHttpPartWithDevice_data() @@ -325,6 +328,35 @@ void tst_QFormDataBuilder::specifyMimeType() QVERIFY(msg.contains(expected_content_type_data)); } +void tst_QFormDataBuilder::picksUtf8NameEncodingIfAsciiDoesNotSuffice_data() +{ + QTest::addColumn("name_data"); + QTest::addColumn("expected_content_disposition_data"); + + QTest::newRow("latin1-ascii") << QAnyStringView("text"_L1) << uR"(form-data; name="text")"_s; + QTest::newRow("u8-ascii") << QAnyStringView(u8"text") << uR"(form-data; name="text")"_s; + QTest::newRow("u-ascii") << QAnyStringView(u"text") << uR"(form-data; name="text")"_s; + + // 0xF6 is 'ö', use hex value with Latin-1 to avoid interpretation as UTF-8 + QTest::newRow("latin1-latin") << QAnyStringView("t\xF6xt"_L1) << uR"(form-data; name="töxt")"_s; + QTest::newRow("u8-latin") << QAnyStringView(u8"töxt") << uR"(form-data; name="töxt")"_s; + QTest::newRow("u-latin") << QAnyStringView(u"töxt") << uR"(form-data; name="töxt")"_s; + + QTest::newRow("u8-u8") << QAnyStringView(u8"テキスト") << uR"(form-data; name="テキスト")"_s; +} + +void tst_QFormDataBuilder::picksUtf8NameEncodingIfAsciiDoesNotSuffice() +{ + QFETCH(const QAnyStringView, name_data); + QFETCH(const QString, expected_content_disposition_data); + + QFormDataBuilder qfdb; + QFormDataPartBuilder &qfdpb = qfdb.part(name_data).setBody("some"_ba); + auto msg = QDebug::toString(qfdpb.build()); + + QVERIFY2(msg.contains(expected_content_disposition_data), + qPrintable(u"content-disposition not found : "_s + expected_content_disposition_data)); +} QTEST_MAIN(tst_QFormDataBuilder) #include "tst_qformdatabuilder.moc"