summaryrefslogtreecommitdiffstats
path: root/src/network/access/qformdatabuilder.cpp
diff options
context:
space:
mode:
authorMate Barany <[email protected]>2023-11-16 17:06:33 +0100
committerMarc Mutz <[email protected]>2024-05-30 18:52:42 +0000
commit32610561e3e7480ea103e730c11e5e3a9675a54a (patch)
tree6d9c47dc60e0e61e2b60f341b2ec5c5b7838dd59 /src/network/access/qformdatabuilder.cpp
parenta5953d20e27ab73774058dd06ac514f9310a41e8 (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.cpp324
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