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 <marc.mutz@qt.io>
This commit is contained in:
Juha Vuolle 2024-06-07 11:46:55 +03:00 committed by Marc Mutz
parent 8c8a0c06d4
commit 6b23a3c5e4
3 changed files with 61 additions and 5 deletions

View File

@ -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<decltype(m_body)>);
static_assert(std::is_nothrow_move_assignable_v<decltype(m_body)>);
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<decltype(m_parts)>);
static_assert(std::is_nothrow_move_assignable_v<decltype(m_parts)>);

View File

@ -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<QHttpMultiPart> buildMultiPart();
private:
std::vector<QFormDataPartBuilder> m_parts;

View File

@ -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<QAnyStringView>("name_data");
QTest::addColumn<QString>("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"