diff options
| author | Mate Barany <[email protected]> | 2023-11-16 17:06:33 +0100 |
|---|---|---|
| committer | Marc Mutz <[email protected]> | 2024-05-30 18:52:42 +0000 |
| commit | 32610561e3e7480ea103e730c11e5e3a9675a54a (patch) | |
| tree | 6d9c47dc60e0e61e2b60f341b2ec5c5b7838dd59 /src/network/access/qformdatabuilder.cpp | |
| parent | a5953d20e27ab73774058dd06ac514f9310a41e8 (diff) | |
Add convenience classes to generate QHttpMultipart messages
Constructing and composing a QHttpMultipart contains some aspects that
are possible candidates for automating, such as setting the headers
manually for each included part. As a reference, when issuing a default
multipart with CURL, one does not need to manually set the headers.
Add the class QFormDataPartBuilder to simplify the construction of
QHttpPart objects.
Add the class QFormDataBuilder to simplify the construction of
QHttpMultiPart objects.
[ChangeLog][QtNetwork][QFormDataBuilder] New class to help constructing
multipart/form-data QHttpMultiParts.
Fixes: QTBUG-114647
Change-Id: Ie035dabc01a9818d65a67c239807b50001fd984a
Reviewed-by: Marc Mutz <[email protected]>
Diffstat (limited to 'src/network/access/qformdatabuilder.cpp')
| -rw-r--r-- | src/network/access/qformdatabuilder.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/network/access/qformdatabuilder.cpp b/src/network/access/qformdatabuilder.cpp new file mode 100644 index 00000000000..8f467e2c576 --- /dev/null +++ b/src/network/access/qformdatabuilder.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2024 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 + +#include "qformdatabuilder.h" + +#if QT_CONFIG(mimetype) +#include "QtCore/qmimedatabase.h" +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QFormDataPartBuilder + \brief The QFormDataPartBuilder class is a convenience class to simplify + the construction of QHttpPart objects. + \since 6.8 + + \ingroup network + \ingroup shared + \inmodule QtNetwork + + The QFormDataPartBuilder class can be used to build a QHttpPart object with + the content disposition header set to be form-data by default. Then the + generated object can be used as part of a multipart message (which is + represented by the QHttpMultiPart class). + + \sa QHttpPart, QHttpMultiPart, QFormDataBuilder +*/ + +/*! + Constructs a QFormDataPartBuilder object and sets \a name as the name + parameter of the form-data. +*/ +QFormDataPartBuilder::QFormDataPartBuilder(QLatin1StringView 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)>); + + m_headerValue += "form-data; name=\""; + for (auto c : name) { + if (c == '"' || c == '\\') + m_headerValue += '\\'; + m_headerValue += c; + } + m_headerValue += "\""; +} + +/*! + \fn QFormDataPartBuilder::QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept + + Move-constructs a QFormDataPartBuilder instance, making it point at the same + object that \a other was pointing to. +*/ + +/*! + \fn QFormDataPartBuilder &QFormDataPartBuilder::operator=(QFormDataPartBuilder &&other) + + Move-assigns \a other to this QFormDataPartBuilder instance. +*/ + +/*! + Destroys the QFormDataPartBuilder object. +*/ + +QFormDataPartBuilder::~QFormDataPartBuilder() + = default; + +static QByteArray buildFileName(QLatin1StringView view) +{ + QByteArray fileName; + fileName += "; filename"; + QByteArrayView encoding = "="; + + for (uchar c : view) { + if (c > 127) { + encoding = "*=ISO-8859-1''"; + break; + } + } + + fileName += encoding; + fileName += QByteArray::fromRawData(view.data(), view.size()).toPercentEncoding(); + return fileName; +} + +static QByteArray buildFileName(QUtf8StringView view) +{ + QByteArrayView bv = view; + QByteArray fileName; + fileName += "; filename"; + QByteArrayView encoding = "="; + + for (uchar c : bv) { + if (c > 127) { + encoding = "*=UTF-8''"; + break; + } + } + + fileName += encoding; + fileName += QByteArray::fromRawData(bv.data(), bv.size()).toPercentEncoding(); + return fileName; +} + +static QByteArray buildFileName(QStringView view) +{ + QByteArray fileName; + fileName += "; filename"; + QByteArrayView encoding = "="; + bool needsUtf8 = false; + + for (QChar c : view) { + if (c > u'\xff') { + encoding = "*=UTF-8''"; + needsUtf8 = true; + break; + } else if (c > u'\x7f') { + encoding = "*=ISO-8859-1''"; + } + } + + fileName += encoding; + + if (needsUtf8) + fileName += view.toUtf8().toPercentEncoding(); + else + fileName += view.toLatin1().toPercentEncoding(); + + return fileName; +} + +QFormDataPartBuilder &QFormDataPartBuilder::setBodyHelper(const QByteArray &data, + QAnyStringView fileName) +{ + if (fileName.isEmpty()) + m_bodyName = QByteArray(); + else + m_bodyName = fileName.visit([&](auto name) { return buildFileName(name); }); + + m_originalBodyName = fileName.toString(); + m_body = data; + return *this; +} + +/*! + Sets \a data as the body of this MIME part and, if given, \a fileName as the + file name parameter in the content disposition header. + + A subsequent call to setBodyDevice() discards the body and the device will + be used instead. + + For a large amount of data (e.g. an image), setBodyDevice() is preferred, + which will not copy the data internally. + + \sa setBodyDevice() +*/ + +QFormDataPartBuilder &QFormDataPartBuilder::setBody(QByteArrayView data, + QAnyStringView fileName) +{ + return setBody(data.toByteArray(), fileName); +} + +/*! + Sets \a body as the body device of this part and \a fileName as the file + name parameter in the content disposition header. + + A subsequent call to setBody() discards the body device and the data set by + setBody() will be used instead. + + For large amounts of data this method should be preferred over setBody(), + because the content is not copied when using this method, but read + directly from the device. + + \a body must be open and readable. QFormDataPartBuilder does not take + ownership of \a body, i.e. the device must be closed and destroyed if + necessary. + + \sa setBody(), QHttpPart::setBodyDevice() + */ + +QFormDataPartBuilder &QFormDataPartBuilder::setBodyDevice(QIODevice *body, QAnyStringView fileName) +{ + if (fileName.isEmpty()) + m_bodyName = QByteArray(); + else + m_bodyName = fileName.visit([&](auto name) { return buildFileName(name); }); + + m_originalBodyName = fileName.toString(); + m_body = body; + return *this; +} + +/*! + Sets the headers specified in \a headers. + + \note The "content-type" and "content-disposition" headers, if any are + specified in \a headers, will be overwritten by the class. +*/ + +QFormDataPartBuilder &QFormDataPartBuilder::setHeaders(const QHttpHeaders &headers) +{ + m_httpHeaders = headers; + return *this; +} + +/*! + Generates a QHttpPart and sets the content disposition header as form-data. + + When this function called, it uses the MIME database to deduce the type the + body based on its name and then sets the deduced type as the content type + header. +*/ + +QHttpPart QFormDataPartBuilder::build() +{ + QHttpPart httpPart; + + if (!m_bodyName.isEmpty()) + m_headerValue += m_bodyName; // RFC 5987 Section 3.2.1 + +#if QT_CONFIG(mimetype) + QMimeDatabase db; + QMimeType mimeType = std::visit([&](auto &arg) { + return db.mimeTypeForFileNameAndData(m_originalBodyName, arg); + }, m_body); +#endif + for (qsizetype i = 0; i < m_httpHeaders.size(); i++) { + httpPart.setRawHeader(QByteArrayView(m_httpHeaders.nameAt(i)).toByteArray(), + m_httpHeaders.valueAt(i).toByteArray()); + } +#if QT_CONFIG(mimetype) + httpPart.setHeader(QNetworkRequest::ContentTypeHeader, mimeType.name()); +#endif + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, m_headerValue); + + + if (auto d = std::get_if<QIODevice*>(&m_body)) + httpPart.setBodyDevice(*d); + else if (auto b = std::get_if<QByteArray>(&m_body)) + httpPart.setBody(*b); + else + Q_UNREACHABLE(); + + return httpPart; +} + +/*! + \class QFormDataBuilder + \brief The QFormDataBuilder class is a convenience class to simplify + the construction of QHttpMultiPart objects. + \since 6.8 + + \ingroup network + \ingroup shared + \inmodule QtNetwork + + The QFormDataBuilder class can be used to build a QHttpMultiPart object + with the content type set to be FormDataType by default. + + \sa QHttpPart, QHttpMultiPart, QFormDataPartBuilder +*/ + +/*! + Constructs an empty QFormDataBuilder object. +*/ + +QFormDataBuilder::QFormDataBuilder() + = default; + +/*! + Destroys the QFormDataBuilder object. +*/ + +QFormDataBuilder::~QFormDataBuilder() + = default; + +/*! + \fn QFormDataBuilder::QFormDataBuilder(QFormDataBuilder &&other) noexcept + + Move-constructs a QFormDataBuilder instance, making it point at the same + object that \a other was pointing to. +*/ + +/*! + \fn QFormDataBuilder &QFormDataBuilder::operator=(QFormDataBuilder &&other) noexcept + + Move-assigns \a other to this QFormDataBuilder instance. +*/ + +/*! + Constructs and returns a reference to a QFormDataPartBuilder object and sets + \a name as the name parameter of the form-data. The returned reference is + valid until the next call to this function. + + \sa QFormDataPartBuilder, QHttpPart +*/ + +QFormDataPartBuilder &QFormDataBuilder::part(QLatin1StringView name) +{ + static_assert(std::is_nothrow_move_constructible_v<decltype(m_parts)>); + static_assert(std::is_nothrow_move_assignable_v<decltype(m_parts)>); + + return m_parts.emplace_back(name, QFormDataPartBuilder::PrivateConstructor()); +} + +/*! + Constructs and returns a pointer to a QHttpMultipart object. The caller + takes ownership of the generated QHttpMultiPart object. + + \sa QHttpMultiPart +*/ + +std::unique_ptr<QHttpMultiPart> QFormDataBuilder::buildMultiPart() +{ + auto multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType); + + for (auto &part : m_parts) + multiPart->append(part.build()); + + return multiPart; +} + +QT_END_NAMESPACE |
