Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qformdatabuilder.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QtCore/private/qstringconverter_p.h>
8#if QT_CONFIG(mimetype)
9#include "QtCore/qmimedatabase.h"
10#endif
11
12#include <variant>
13#include <vector>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \class QFormDataPartBuilder
19 \brief The QFormDataPartBuilder class is a convenience class to simplify
20 the construction of QHttpPart objects.
21 \since 6.8
22
23 \ingroup network
24 \ingroup shared
25 \inmodule QtNetwork
26
27 The QFormDataPartBuilder class can be used to build a QHttpPart object with
28 the content disposition header set to be form-data by default. Then the
29 generated object can be used as part of a multipart message (which is
30 represented by the QHttpMultiPart class).
31
32 \sa QHttpPart, QHttpMultiPart, QFormDataBuilder
33*/
34
35class QFormDataPartBuilderPrivate
36{
37public:
38 explicit QFormDataPartBuilderPrivate(QAnyStringView name);
39 QHttpPart build(QFormDataBuilder::Options options);
40
41 QString m_name;
42 QByteArray m_mimeType;
43 QString m_originalBodyName;
44 QHttpHeaders m_httpHeaders;
45 std::variant<QIODevice*, QByteArray> m_body;
46};
47
48QFormDataPartBuilderPrivate::QFormDataPartBuilderPrivate(QAnyStringView name)
49 : m_name{name.toString()}
50{
51
52}
53
54
55static void escapeNameAndAppend(QByteArray &dst, QByteArrayView src)
56{
57 for (auto c : src) {
58 if (c == '"' || c == '\\')
59 dst += '\\';
60 dst += c;
61 }
62}
63
64static void escapeNameAndAppend(QByteArray &dst, QStringView src)
65{
66 qsizetype last = 0;
67
68 // equivalent to for (auto chunk : qTokenize(src, any_of("\\\""))), if there was such a thing
69 for (qsizetype i = 0, end = src.size(); i != end; ++i) {
70 const auto c = src[i];
71 if (c == u'"' || c == u'\\') {
72 const auto chunk = src.sliced(last, i - last);
73 dst += QUtf8::convertFromUnicode(chunk); // ### optimize
74 dst += '\\';
75 last = i;
76 }
77 }
78 dst += QUtf8::convertFromUnicode(src.sliced(last));
79}
80
81/*!
82 \fn QFormDataPartBuilder::QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept
83
84 Move-constructs a QFormDataPartBuilder instance, making it point at the same
85 object that \a other was pointing to.
86*/
87
88/*!
89 \fn QFormDataPartBuilder &QFormDataPartBuilder::operator=(QFormDataPartBuilder &&other)
90
91 Move-assigns \a other to this QFormDataPartBuilder instance.
92*/
93
94/*!
95 \fn QFormDataPartBuilder::QFormDataPartBuilder(const QFormDataPartBuilder &other)
96
97 Constructs a copy of \a other. The object is valid for as long as the associated
98 QFormDataBuilder has not been destroyed.
99
100 The data of the copy is shared (shallow copy): modifying one part will also change
101 the other.
102
103 \code
104 QFormDataPartBuilder foo()
105 {
106 QFormDataBuilder builder;
107 auto qfdpb1 = builder.part("First"_L1);
108 auto qfdpb2 = qfdpb1; // this creates a shallow copy
109
110 qfdpb2.setBodyDevice(&image, "cutecat.jpg"); // qfdpb1 is also modified
111
112 return qfdbp2; // invalid, builder is destroyed at the end of the scope
113 }
114 \endcode
115*/
116
117/*!
118 \fn QFormDataPartBuilder& QFormDataPartBuilder::operator=(const QFormDataPartBuilder &other)
119
120 Assigns \a other to QFormDataPartBuilder and returns a reference to this
121 QFormDataPartBuilder. The object is valid for as long as the associated QFormDataBuilder
122 has not been destroyed.
123
124 The data of the copy is shared (shallow copy): modifying one part will also change the other.
125
126 \code
127 QFormDataPartBuilder foo()
128 {
129 QFormDataBuilder builder;
130 auto qfdpb1 = builder.part("First"_L1);
131 auto qfdpb2 = qfdpb1; // this creates a shallow copy
132
133 qfdpb2.setBodyDevice(&image, "cutecat.jpg"); // qfdpb1 is also modified
134
135 return qfdbp2; // invalid, builder is destroyed at the end of the scope
136 }
137 \endcode
138*/
139
140/*!
141 \fn QFormDataPartBuilder::~QFormDataPartBuilder()
142
143 Destroys the QFormDataPartBuilder object.
144*/
145
146static void convertInto_impl(QByteArray &dst, QUtf8StringView in)
147{
148 dst.clear();
149 dst += QByteArrayView{in}; // it's ASCII, anyway
150}
151
152static void convertInto_impl(QByteArray &dst, QLatin1StringView in)
153{
154 dst.clear();
155 dst += QByteArrayView{in}; // it's ASCII, anyway
156}
157
158static void convertInto_impl(QByteArray &dst, QStringView in)
159{
160 dst.resize(in.size());
161 (void)QLatin1::convertFromUnicode(dst.data(), in);
162}
163
164static void convertInto(QByteArray &dst, QAnyStringView in)
165{
166 in.visit([&dst](auto in) { convertInto_impl(dst, in); });
167}
168
169QFormDataPartBuilder QFormDataPartBuilder::setBodyHelper(const QByteArray &data,
170 QAnyStringView name,
171 QAnyStringView mimeType)
172{
173 Q_D(QFormDataPartBuilder);
174
175 d->m_originalBodyName = name.toString();
176 convertInto(d->m_mimeType, mimeType);
177 d->m_body = data;
178 return *this;
179}
180
181/*!
182 Sets \a data as the body of this MIME part and, if given, \a fileName as the
183 file name parameter in the content disposition header.
184
185 If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
186 auto-detect the mime-type of \a data using QMimeDatabase.
187
188 A subsequent call to setBodyDevice() discards the body and the device will
189 be used instead.
190
191 For a large amount of data (e.g. an image), setBodyDevice() is preferred,
192 which will not copy the data internally.
193
194 \sa setBodyDevice()
195*/
196
197QFormDataPartBuilder QFormDataPartBuilder::setBody(QByteArrayView data,
198 QAnyStringView fileName,
199 QAnyStringView mimeType)
200{
201 return setBody(data.toByteArray(), fileName, mimeType);
202}
203
204/*!
205 Sets \a body as the body device of this part and \a fileName as the file
206 name parameter in the content disposition header.
207
208 If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
209 auto-detect the mime-type of \a body using QMimeDatabase.
210
211 A subsequent call to setBody() discards the body device and the data set by
212 setBody() will be used instead.
213
214 For large amounts of data this method should be preferred over setBody(),
215 because the content is not copied when using this method, but read
216 directly from the device.
217
218 \a body must be open and readable. QFormDataPartBuilder does not take
219 ownership of \a body, i.e. the device must be closed and destroyed if
220 necessary.
221
222 \note If \a body is sequential (e.g. sockets, but not files),
223 QNetworkAccessManager::post() should be called after \a body has emitted
224 finished().
225
226 \sa setBody(), QHttpPart::setBodyDevice()
227 */
228
229QFormDataPartBuilder QFormDataPartBuilder::setBodyDevice(QIODevice *body, QAnyStringView fileName,
230 QAnyStringView mimeType)
231{
232 Q_D(QFormDataPartBuilder);
233
234 d->m_originalBodyName = fileName.toString();
235 convertInto(d->m_mimeType, mimeType);
236 d->m_body = body;
237 return *this;
238}
239
240/*!
241 Sets the headers specified in \a headers.
242
243 \note The "content-type" and "content-disposition" headers, if any are
244 specified in \a headers, will be overwritten by the class.
245*/
246
247QFormDataPartBuilder QFormDataPartBuilder::setHeaders(const QHttpHeaders &headers)
248{
249 Q_D(QFormDataPartBuilder);
250
251 d->m_httpHeaders = headers;
252 return *this;
253}
254
255/*!
256 \internal
257
258 Generates a QHttpPart and sets the content disposition header as form-data.
259
260 When this function called, it uses the MIME database to deduce the type the
261 body based on its name and then sets the deduced type as the content type
262 header.
263*/
264
265QHttpPart QFormDataPartBuilderPrivate::build(QFormDataBuilder::Options options)
266{
267 QHttpPart httpPart;
268
269 using Opt = QFormDataBuilder::Option;
270
271 QByteArray headerValue;
272
273 headerValue += "form-data; name=\"";
274 escapeNameAndAppend(headerValue, m_name);
275 headerValue += "\"";
276
277 if (!m_originalBodyName.isNull()) {
278
279 enum class Encoding { ASCII, Latin1, Utf8 } encoding = Encoding::ASCII;
280 for (QChar c : std::as_const(m_originalBodyName)) {
281 if (c > u'\xff') {
282 encoding = Encoding::Utf8;
283 break;
284 } else if (c > u'\x7f') {
285 encoding = Encoding::Latin1;
286 }
287 }
288 QByteArray enc;
289 if ((options & Opt::PreferLatin1EncodedFilename) && encoding != Encoding::Utf8)
290 enc = m_originalBodyName.toLatin1();
291 else
292 enc = m_originalBodyName.toUtf8();
293
294 headerValue += "; filename=\"";
295 if (options & Opt::UseRfc7578PercentEncodedFilename)
296 headerValue += enc.toPercentEncoding();
297 else
298 escapeNameAndAppend(headerValue, enc);
299 headerValue += "\"";
300 if (encoding != Encoding::ASCII && !(options & Opt::OmitRfc8187EncodedFilename)) {
301 // For 'filename*' production see
302 // https://datatracker.ietf.org/doc/html/rfc5987#section-3.2.1
303 // For providing both filename and filename* parameters see
304 // https://datatracker.ietf.org/doc/html/rfc6266#section-4.3 and
305 // https://datatracker.ietf.org/doc/html/rfc8187#section-4.2
306 if ((options & Opt::PreferLatin1EncodedFilename) && encoding == Encoding::Latin1)
307 headerValue += "; filename*=ISO-8859-1''";
308 else
309 headerValue += "; filename*=UTF-8''";
310 headerValue += enc.toPercentEncoding();
311 }
312 }
313
314#if QT_CONFIG(mimetype)
315 if (m_mimeType.isEmpty()) {
316 // auto-detect
317 QMimeDatabase db;
318 convertInto(m_mimeType, std::visit([&](auto &arg) {
319 return db.mimeTypeForFileNameAndData(m_originalBodyName, arg);
320 }, m_body).name());
321 }
322#endif
323
324 for (qsizetype i = 0; i < m_httpHeaders.size(); i++) {
325 httpPart.setRawHeader(QByteArrayView(m_httpHeaders.nameAt(i)).toByteArray(),
326 m_httpHeaders.valueAt(i).toByteArray());
327 }
328
329 if (!m_mimeType.isEmpty())
330 httpPart.setHeader(QNetworkRequest::ContentTypeHeader, m_mimeType);
331
332 httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, std::move(headerValue));
333
334 if (auto d = std::get_if<QIODevice*>(&m_body))
335 httpPart.setBodyDevice(*d);
336 else if (auto b = std::get_if<QByteArray>(&m_body))
337 httpPart.setBody(*b);
338 else
339 Q_UNREACHABLE();
340
341 return httpPart;
342}
343
344/*!
345 \class QFormDataBuilder
346 \brief The QFormDataBuilder class is a convenience class to simplify
347 the construction of QHttpMultiPart objects.
348 \since 6.8
349
350 \ingroup network
351 \ingroup shared
352 \inmodule QtNetwork
353
354 The QFormDataBuilder class can be used to build a QHttpMultiPart object
355 with the content type set to be FormDataType by default.
356
357 The snippet below demonstrates how to build a multipart message with
358 QFormDataBuilder:
359
360 \code
361 QFormDataBuilder builder;
362 QFile image(u"../../pic.png"_s); image.open(QFile::ReadOnly);
363 QFile mask(u"../../mask.png"_s); mask.open(QFile::ReadOnly);
364
365 builder.part("image"_L1).setBodyDevice(&image, "the actual image");
366 builder.part("mask"_L1).setBodyDevice(&mask, "the mask image");
367 builder.part("prompt"_L1).setBody("Lobster wearing a beret");
368 builder.part("n"_L1).setBody("2");
369 builder.part("size"_L1).setBody("512x512");
370
371 std::unique_ptr<QHttpMultiPart> mp = builder.buildMultiPart();
372 \endcode
373
374 \sa QHttpPart, QHttpMultiPart, QFormDataPartBuilder
375*/
376
382
383QFormDataPartBuilderPrivate* QFormDataPartBuilder::d_func()
384{
385 return const_cast<QFormDataPartBuilderPrivate*>(std::as_const(*this).d_func());
386}
387
388const QFormDataPartBuilderPrivate* QFormDataPartBuilder::d_func() const
389{
390 Q_ASSERT(m_index < d->parts.size());
391 return &d->parts[m_index];
392}
393
394/*!
395 \enum QFormDataBuilder::Option
396
397 Options controlling buildMultiPart().
398
399 Several current RFCs disagree on how, exactly, to format
400 \c{multipart/form-data}. Instead of hard-coding any one RFC, these options
401 give you control over which RFC to follow.
402
403 \value Default The default values, designed to maximize interoperability in
404 general. All options named below are off.
405
406 \value OmitRfc8187EncodedFilename
407 When a body-part's file-name contains non-US-ASCII characters,
408 \l{https://datatracker.ietf.org/doc/html/rfc6266#section-4.3}
409 {RFC 6266 Section 4.3} suggests to use
410 \l{https://datatracker.ietf.org/doc/html/rfc8187}{RFC 8187}-style
411 encoding (\c{filename*=utf-8''...}). The more recent
412 \l{https://datatracker.ietf.org/doc/html/rfc7578#section-4.2}
413 {RFC 7578 Section 4.2}, however, bans the use of that mechanism.
414 Both RFCs are current as of this writing, so this option allows you
415 to choose which one to follow. The default is to include the
416 RFC 8187-encoded \c{filename*} alongside the unencoded \c{filename},
417 as suggested by RFC 6266.
418
419 \value UseRfc7578PercentEncodedFilename
420 When a body-part's file-name contains non-US-ASCII characters,
421 \l{https://datatracker.ietf.org/doc/html/rfc7578#section-4.2}
422 {RFC 7578 Section 4.2} suggests to use percent-encoding of the octets
423 of the UTF-8-encoded file-name. It goes on to note that many
424 implementations, however, do \e{not} percent-encode the UTF-8-encoded
425 file-name, but just emit "raw" UTF-8 (with \c{"} and \c{\} escaped
426 using \c{\}). This is the default of QFormDataBuilder, too.
427
428 \value PreferLatin1EncodedFilename
429 \l{https://datatracker.ietf.org/doc/html/rfc5987#section-3.2}
430 {RFC 5987 Section 3.2} required recipients to support ISO-8859-1
431 ("Latin-1") encoding. When a body-part's file-name contains
432 non-US-ASCII characters that, however, fit into Latin-1, this option
433 prefers to use ISO-8859-1 encoding over UTF-8. The more recent
434 \{https://datatracker.ietf.org/doc/html/rfc8187#appendix-A}{RFC 8187}
435 no longer requires ISO-8859-1 support, so the default is to send all
436 non-US-ASCII file-names in UTF-8 encoding instead.
437
438 \value StrictRfc7578
439 This option combines other options to select strict
440 \l{https://datatracker.ietf.org/doc/html/rfc7578}{RFC 7578}
441 compliance.
442*/
443
444/*!
445 Constructs an empty QFormDataBuilder object.
446*/
447
448QFormDataBuilder::QFormDataBuilder()
449 : d_ptr(new QFormDataBuilderPrivate())
450{
451
452}
453
454/*!
455 Destroys the QFormDataBuilder object.
456*/
457
458QFormDataBuilder::~QFormDataBuilder()
459{
460 delete d_ptr;
461}
462
463/*!
464 \fn QFormDataBuilder::QFormDataBuilder(QFormDataBuilder &&other) noexcept
465
466 Move-constructs a QFormDataBuilder instance, making it point at the same
467 object that \a other was pointing to.
468*/
469
470/*!
471 \fn QFormDataBuilder &QFormDataBuilder::operator=(QFormDataBuilder &&other) noexcept
472
473 Move-assigns \a other to this QFormDataBuilder instance.
474*/
475
476/*!
477 Returns a newly-constructed QFormDataPartBuilder object using \a name as the
478 form-data's \c name parameter. The object is valid for as long as the
479 associated QFormDataBuilder has not been destroyed.
480
481 Limiting \a name characters to US-ASCII is
482 \l {https://datatracker.ietf.org/doc/html/rfc7578#section-5.1.1}{strongly recommended}
483 for interoperability reasons.
484
485 \sa QFormDataPartBuilder, QHttpPart
486*/
487
488QFormDataPartBuilder QFormDataBuilder::part(QAnyStringView name)
489{
490 Q_D(QFormDataBuilder);
491
492 d->parts.emplace_back(name);
493 return QFormDataPartBuilder(d, d->parts.size() - 1);
494}
495
496/*!
497 Constructs and returns a pointer to a QHttpMultipart object constructed
498 according to \a options.
499
500 \sa QHttpMultiPart
501*/
502
503std::unique_ptr<QHttpMultiPart> QFormDataBuilder::buildMultiPart(Options options)
504{
505 Q_D(QFormDataBuilder);
506
507 auto multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType);
508
509 for (auto &part : d->parts)
510 multiPart->append(part.build(options));
511
512 return multiPart;
513}
514
515QT_END_NAMESPACE
std::vector< QFormDataPartBuilderPrivate > parts
The QFormDataBuilder class is a convenience class to simplify the construction of QHttpMultiPart obje...
The QFormDataPartBuilder class is a convenience class to simplify the construction of QHttpPart objec...
static void convertInto_impl(QByteArray &dst, QUtf8StringView in)
static void convertInto(QByteArray &dst, QAnyStringView in)
static void escapeNameAndAppend(QByteArray &dst, QByteArrayView src)