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