1// Copyright (C) 2016 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
4#include "qhttpmultipart.h"
5#include "qhttpmultipart_p.h"
6#include "QtCore/qdatetime.h" // for initializing the random number generator with QTime
7#include "QtCore/qmutex.h"
8#include "QtCore/qrandom.h"
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \class QHttpPart
14 \brief The QHttpPart class holds a body part to be used inside a
15 HTTP multipart MIME message.
16 \since 4.8
17
18 \ingroup network
19 \ingroup shared
20 \inmodule QtNetwork
21
22 The QHttpPart class holds a body part to be used inside a HTTP
23 multipart MIME message (which is represented by the QHttpMultiPart class).
24 A QHttpPart consists of a header block
25 and a data block, which are separated by each other by two
26 consecutive new lines. An example for one part would be:
27
28 \snippet code/src_network_access_qhttppart.cpp 0
29
30 For setting headers, use setHeader() and setRawHeader(), which behave
31 exactly like QNetworkRequest::setHeader() and QNetworkRequest::setRawHeader().
32
33 For reading small pieces of data, use setBody(); for larger data blocks
34 like e.g. images, use setBodyDevice(). The latter method saves memory by
35 not copying the data internally, but reading directly from the device.
36 This means that the device must be opened and readable at the moment when
37 the multipart message containing the body part is sent on the network via
38 QNetworkAccessManager::post().
39
40 To construct a QHttpPart with a small body, consider the following snippet
41 (this produces the data shown in the example above):
42
43 \snippet code/src_network_access_qhttppart.cpp 1
44
45 To construct a QHttpPart reading from a device (e.g. a file), the following
46 can be applied:
47
48 \snippet code/src_network_access_qhttppart.cpp 2
49
50 Be aware that QHttpPart does not take ownership of the device when set, so
51 it is the developer's responsibility to destroy it when it is not needed anymore.
52 A good idea might be to set the multipart message as parent object for the device,
53 as documented at the documentation for QHttpMultiPart.
54
55 \sa QHttpMultiPart, QNetworkAccessManager
56*/
57
58
59/*!
60 Constructs an empty QHttpPart object.
61*/
62QHttpPart::QHttpPart() : d(new QHttpPartPrivate)
63{
64}
65
66/*!
67 Creates a copy of \a other.
68*/
69QHttpPart::QHttpPart(const QHttpPart &other) : d(other.d)
70{
71}
72
73/*!
74 Destroys this QHttpPart.
75*/
76QHttpPart::~QHttpPart()
77{
78 d = nullptr;
79}
80
81/*!
82 Creates a copy of \a other.
83*/
84QHttpPart &QHttpPart::operator=(const QHttpPart &other)
85{
86 d = other.d;
87 return *this;
88}
89
90/*!
91 \fn void QHttpPart::swap(QHttpPart &other)
92 \since 5.0
93
94 Swaps this HTTP part with \a other. This function is very fast and
95 never fails.
96*/
97
98/*!
99 Returns \c true if this object is the same as \a other (i.e., if they
100 have the same headers and body).
101
102 \sa operator!=()
103*/
104bool QHttpPart::operator==(const QHttpPart &other) const
105{
106 return d == other.d || *d == *other.d;
107}
108
109/*!
110 \fn bool QHttpPart::operator!=(const QHttpPart &other) const
111
112 Returns \c true if this object is not the same as \a other.
113
114 \sa operator==()
115*/
116
117/*!
118 Sets the value of the known header \a header to be \a value,
119 overriding any previously set headers.
120
121 \sa QNetworkRequest::KnownHeaders, setRawHeader(), QNetworkRequest::setHeader()
122*/
123void QHttpPart::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)
124{
125 d->setCookedHeader(header, value);
126}
127
128/*!
129 Sets the header \a headerName to be of value \a headerValue. If \a
130 headerName corresponds to a known header (see
131 QNetworkRequest::KnownHeaders), the raw format will be parsed and
132 the corresponding "cooked" header will be set as well.
133
134 \note Setting the same header twice overrides the previous
135 setting. To accomplish the behaviour of multiple HTTP headers of
136 the same name, you should concatenate the two values, separating
137 them with a comma (",") and set one single raw header.
138
139 \sa QNetworkRequest::KnownHeaders, setHeader(), QNetworkRequest::setRawHeader()
140*/
141void QHttpPart::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
142{
143 d->setRawHeader(key: headerName, value: headerValue);
144}
145
146/*!
147 Sets the body of this MIME part to \a body. The body set with this method
148 will be used unless the device is set via setBodyDevice(). For a large
149 amount of data (e.g. an image), use setBodyDevice(), which will not copy
150 the data internally.
151
152 \sa setBodyDevice()
153*/
154void QHttpPart::setBody(const QByteArray &body)
155{
156 d->setBody(body);
157}
158
159/*!
160 Sets the device to read the content from to \a device. For large amounts of data
161 this method should be preferred over setBody(),
162 because the content is not copied when using this method, but read
163 directly from the device.
164 \a device must be open and readable. QHttpPart does not take ownership
165 of \a device, i.e. the device must be closed and destroyed if necessary.
166 if \a device is sequential (e.g. sockets, but not files),
167 QNetworkAccessManager::post() should be called after \a device has
168 emitted finished().
169 For unsetting the device and using data set via setBody(), use
170 "setBodyDevice(0)".
171
172 \sa setBody(), QNetworkAccessManager::post()
173 */
174void QHttpPart::setBodyDevice(QIODevice *device)
175{
176 d->setBodyDevice(device);
177}
178
179
180
181/*!
182 \class QHttpMultiPart
183 \brief The QHttpMultiPart class resembles a MIME multipart message to be sent over HTTP.
184 \since 4.8
185
186 \ingroup network
187 \inmodule QtNetwork
188
189 The QHttpMultiPart resembles a MIME multipart message, as described in RFC 2046,
190 which is to be sent over HTTP.
191 A multipart message consists of an arbitrary number of body parts (see QHttpPart),
192 which are separated by a unique boundary. The boundary of the QHttpMultiPart is
193 constructed with the string "boundary_.oOo._" followed by random characters,
194 and provides enough uniqueness to make sure it does not occur inside the parts itself.
195 If desired, the boundary can still be set via setBoundary().
196
197 As an example, consider the following code snippet, which constructs a multipart
198 message containing a text part followed by an image part:
199
200 \snippet code/src_network_access_qhttpmultipart.cpp 0
201
202 \sa QHttpPart, QNetworkAccessManager::post()
203*/
204
205/*!
206 \enum QHttpMultiPart::ContentType
207
208 List of known content types for a multipart subtype as described
209 in RFC 2046 and others.
210
211 \value MixedType corresponds to the "multipart/mixed" subtype,
212 meaning the body parts are independent of each other, as described
213 in RFC 2046.
214
215 \value RelatedType corresponds to the "multipart/related" subtype,
216 meaning the body parts are related to each other, as described in RFC 2387.
217
218 \value FormDataType corresponds to the "multipart/form-data"
219 subtype, meaning the body parts contain form elements, as described in RFC 2388.
220
221 \value AlternativeType corresponds to the "multipart/alternative"
222 subtype, meaning the body parts are alternative representations of
223 the same information, as described in RFC 2046.
224
225 \sa setContentType()
226*/
227
228/*!
229 Constructs a QHttpMultiPart with content type MixedType and sets
230 \a parent as the parent object.
231
232 \sa QHttpMultiPart::ContentType
233*/
234QHttpMultiPart::QHttpMultiPart(QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
235{
236 Q_D(QHttpMultiPart);
237 d->contentType = MixedType;
238}
239
240/*!
241 Constructs a QHttpMultiPart with content type \a contentType and
242 sets parent as the parent object.
243
244 \sa QHttpMultiPart::ContentType
245*/
246QHttpMultiPart::QHttpMultiPart(QHttpMultiPart::ContentType contentType, QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
247{
248 Q_D(QHttpMultiPart);
249 d->contentType = contentType;
250}
251
252/*!
253 Destroys the multipart.
254*/
255QHttpMultiPart::~QHttpMultiPart()
256{
257}
258
259/*!
260 Appends \a httpPart to this multipart.
261*/
262void QHttpMultiPart::append(const QHttpPart &httpPart)
263{
264 d_func()->parts.append(t: httpPart);
265}
266
267/*!
268 Sets the content type to \a contentType. The content type will be used
269 in the HTTP header section when sending the multipart message via
270 QNetworkAccessManager::post().
271 In case you want to use a multipart subtype not contained in
272 QHttpMultiPart::ContentType,
273 you can add the "Content-Type" header field to the QNetworkRequest
274 by hand, and then use this request together with the multipart
275 message for posting.
276
277 \sa QHttpMultiPart::ContentType, QNetworkAccessManager::post()
278*/
279void QHttpMultiPart::setContentType(QHttpMultiPart::ContentType contentType)
280{
281 d_func()->contentType = contentType;
282}
283
284/*!
285 returns the boundary.
286
287 \sa setBoundary()
288*/
289QByteArray QHttpMultiPart::boundary() const
290{
291 return d_func()->boundary;
292}
293
294/*!
295 Sets the boundary to \a boundary.
296
297 Usually, you do not need to generate a boundary yourself; upon construction
298 the boundary is initiated with the string "boundary_.oOo._" followed by random
299 characters, and provides enough uniqueness to make sure it does not occur
300 inside the parts itself.
301
302 \sa boundary()
303*/
304void QHttpMultiPart::setBoundary(const QByteArray &boundary)
305{
306 d_func()->boundary = boundary;
307}
308
309
310
311// ------------------------------------------------------------------
312// ----------- implementations of private classes: ------------------
313// ------------------------------------------------------------------
314
315
316
317qint64 QHttpPartPrivate::bytesAvailable() const
318{
319 checkHeaderCreated();
320 qint64 bytesAvailable = header.size();
321 if (bodyDevice) {
322 bytesAvailable += bodyDevice->bytesAvailable() - readPointer;
323 } else {
324 bytesAvailable += body.size() - readPointer;
325 }
326 // the device might have closed etc., so make sure we do not return a negative value
327 return qMax(a: bytesAvailable, b: (qint64) 0);
328}
329
330qint64 QHttpPartPrivate::readData(char *data, qint64 maxSize)
331{
332 checkHeaderCreated();
333 qint64 bytesRead = 0;
334 qint64 headerDataCount = header.size();
335
336 // read header if it has not been read yet
337 if (readPointer < headerDataCount) {
338 bytesRead = qMin(a: headerDataCount - readPointer, b: maxSize);
339 const char *headerData = header.constData();
340 memcpy(dest: data, src: headerData + readPointer, n: bytesRead);
341 readPointer += bytesRead;
342 }
343 // read content if there is still space
344 if (bytesRead < maxSize) {
345 if (bodyDevice) {
346 qint64 dataBytesRead = bodyDevice->read(data: data + bytesRead, maxlen: maxSize - bytesRead);
347 if (dataBytesRead == -1)
348 return -1;
349 bytesRead += dataBytesRead;
350 readPointer += dataBytesRead;
351 } else {
352 qint64 contentBytesRead = qMin(a: body.size() - readPointer + headerDataCount, b: maxSize - bytesRead);
353 const char *contentData = body.constData();
354 // if this method is called several times, we need to find the
355 // right offset in the content ourselves:
356 memcpy(dest: data + bytesRead, src: contentData + readPointer - headerDataCount, n: contentBytesRead);
357 bytesRead += contentBytesRead;
358 readPointer += contentBytesRead;
359 }
360 }
361 return bytesRead;
362}
363
364qint64 QHttpPartPrivate::size() const
365{
366 checkHeaderCreated();
367 qint64 size = header.size();
368 if (bodyDevice) {
369 size += bodyDevice->size();
370 } else {
371 size += body.size();
372 }
373 return size;
374}
375
376bool QHttpPartPrivate::reset()
377{
378 bool ret = true;
379 if (bodyDevice)
380 if (!bodyDevice->reset())
381 ret = false;
382 readPointer = 0;
383 return ret;
384}
385void QHttpPartPrivate::checkHeaderCreated() const
386{
387 if (!headerCreated) {
388 // copied from QHttpNetworkRequestPrivate::header() and adapted
389 QList<QPair<QByteArray, QByteArray> > fields = allRawHeaders();
390 QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
391 for (; it != fields.constEnd(); ++it)
392 header += it->first + ": " + it->second + "\r\n";
393 header += "\r\n";
394 headerCreated = true;
395 }
396}
397
398QHttpMultiPartPrivate::QHttpMultiPartPrivate() : contentType(QHttpMultiPart::MixedType), device(new QHttpMultiPartIODevice(this))
399{
400 // 24 random bytes, becomes 32 characters when encoded to Base64
401 quint32 random[6];
402 QRandomGenerator::global()->fillRange(buffer&: random);
403 boundary = "boundary_.oOo._"
404 + QByteArray::fromRawData(data: reinterpret_cast<char *>(random), size: sizeof(random)).toBase64();
405
406 // boundary must not be longer than 70 characters, see RFC 2046, section 5.1.1
407 Q_ASSERT(boundary.size() <= 70);
408}
409
410qint64 QHttpMultiPartIODevice::size() const
411{
412 // if not done yet, we calculate the size and the offsets of each part,
413 // including boundary (needed later in readData)
414 if (deviceSize == -1) {
415 qint64 currentSize = 0;
416 qint64 boundaryCount = multiPart->boundary.size();
417 for (int a = 0; a < multiPart->parts.size(); a++) {
418 partOffsets.append(t: currentSize);
419 // 4 additional bytes for the "--" before and the "\r\n" after the boundary,
420 // and 2 bytes for the "\r\n" after the content
421 currentSize += boundaryCount + 4 + multiPart->parts.at(i: a).d->size() + 2;
422 }
423 currentSize += boundaryCount + 6; // size for ending boundary, 2 beginning and ending dashes and "\r\n"
424 deviceSize = currentSize;
425 }
426 return deviceSize;
427}
428
429bool QHttpMultiPartIODevice::isSequential() const
430{
431 for (int a = 0; a < multiPart->parts.size(); a++) {
432 QIODevice *device = multiPart->parts.at(i: a).d->bodyDevice;
433 // we are sequential if any of the bodyDevices of our parts are sequential;
434 // when reading from a byte array, we are not sequential
435 if (device && device->isSequential())
436 return true;
437 }
438 return false;
439}
440
441bool QHttpMultiPartIODevice::reset()
442{
443 // Reset QIODevice's data
444 QIODevice::reset();
445 for (int a = 0; a < multiPart->parts.size(); a++)
446 if (!multiPart->parts[a].d->reset())
447 return false;
448 readPointer = 0;
449 return true;
450}
451qint64 QHttpMultiPartIODevice::readData(char *data, qint64 maxSize)
452{
453 qint64 bytesRead = 0, index = 0;
454
455 // skip the parts we have already read
456 while (index < multiPart->parts.size() &&
457 readPointer >= partOffsets.at(i: index) + multiPart->parts.at(i: index).d->size()
458 + multiPart->boundary.size() + 6) // 6 == 2 boundary dashes, \r\n after boundary, \r\n after multipart
459 index++;
460
461 // read the data
462 while (bytesRead < maxSize && index < multiPart->parts.size()) {
463
464 // check whether we need to read the boundary of the current part
465 QByteArray boundaryData = "--" + multiPart->boundary + "\r\n";
466 qint64 boundaryCount = boundaryData.size();
467 qint64 partIndex = readPointer - partOffsets.at(i: index);
468 if (partIndex < boundaryCount) {
469 qint64 boundaryBytesRead = qMin(a: boundaryCount - partIndex, b: maxSize - bytesRead);
470 memcpy(dest: data + bytesRead, src: boundaryData.constData() + partIndex, n: boundaryBytesRead);
471 bytesRead += boundaryBytesRead;
472 readPointer += boundaryBytesRead;
473 partIndex += boundaryBytesRead;
474 }
475
476 // check whether we need to read the data of the current part
477 if (bytesRead < maxSize && partIndex >= boundaryCount && partIndex < boundaryCount + multiPart->parts.at(i: index).d->size()) {
478 qint64 dataBytesRead = multiPart->parts[index].d->readData(data: data + bytesRead, maxSize: maxSize - bytesRead);
479 if (dataBytesRead == -1)
480 return -1;
481 bytesRead += dataBytesRead;
482 readPointer += dataBytesRead;
483 partIndex += dataBytesRead;
484 }
485
486 // check whether we need to read the ending CRLF of the current part
487 if (bytesRead < maxSize && partIndex >= boundaryCount + multiPart->parts.at(i: index).d->size()) {
488 if (bytesRead == maxSize - 1)
489 return bytesRead;
490 memcpy(dest: data + bytesRead, src: "\r\n", n: 2);
491 bytesRead += 2;
492 readPointer += 2;
493 index++;
494 }
495 }
496 // check whether we need to return the final boundary
497 if (bytesRead < maxSize && index == multiPart->parts.size()) {
498 QByteArray finalBoundary = "--" + multiPart->boundary + "--\r\n";
499 qint64 boundaryIndex = readPointer + finalBoundary.size() - size();
500 qint64 lastBoundaryBytesRead = qMin(a: finalBoundary.size() - boundaryIndex, b: maxSize - bytesRead);
501 memcpy(dest: data + bytesRead, src: finalBoundary.constData() + boundaryIndex, n: lastBoundaryBytesRead);
502 bytesRead += lastBoundaryBytesRead;
503 readPointer += lastBoundaryBytesRead;
504 }
505 return bytesRead;
506}
507
508qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
509{
510 Q_UNUSED(data);
511 Q_UNUSED(maxSize);
512 return -1;
513}
514
515
516QT_END_NAMESPACE
517
518#include "moc_qhttpmultipart.cpp"
519

source code of qtbase/src/network/access/qhttpmultipart.cpp