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 const auto h = headers();
390 for (qsizetype i = 0; i < h.size(); ++i) {
391 const auto name = h.nameAt(i);
392 header += QByteArrayView(name.data(), name.size()) + ": " + h.valueAt(i) + "\r\n";
393 }
394
395 header += "\r\n";
396 headerCreated = true;
397 }
398}
399
400QHttpMultiPartPrivate::QHttpMultiPartPrivate() : contentType(QHttpMultiPart::MixedType), device(new QHttpMultiPartIODevice(this))
401{
402 // 24 random bytes, becomes 32 characters when encoded to Base64
403 quint32 random[6];
404 QRandomGenerator::global()->fillRange(buffer&: random);
405 boundary = "boundary_.oOo._"
406 + QByteArray::fromRawData(data: reinterpret_cast<char *>(random), size: sizeof(random)).toBase64();
407
408 // boundary must not be longer than 70 characters, see RFC 2046, section 5.1.1
409 Q_ASSERT(boundary.size() <= 70);
410}
411
412QHttpMultiPartPrivate::~QHttpMultiPartPrivate()
413{
414 delete device;
415}
416
417QHttpMultiPartIODevice::~QHttpMultiPartIODevice()
418 = default;
419
420qint64 QHttpMultiPartIODevice::size() const
421{
422 // if not done yet, we calculate the size and the offsets of each part,
423 // including boundary (needed later in readData)
424 if (deviceSize == -1) {
425 qint64 currentSize = 0;
426 qint64 boundaryCount = multiPart->boundary.size();
427 for (int a = 0; a < multiPart->parts.size(); a++) {
428 partOffsets.append(t: currentSize);
429 // 4 additional bytes for the "--" before and the "\r\n" after the boundary,
430 // and 2 bytes for the "\r\n" after the content
431 currentSize += boundaryCount + 4 + multiPart->parts.at(i: a).d->size() + 2;
432 }
433 currentSize += boundaryCount + 6; // size for ending boundary, 2 beginning and ending dashes and "\r\n"
434 deviceSize = currentSize;
435 }
436 return deviceSize;
437}
438
439bool QHttpMultiPartIODevice::isSequential() const
440{
441 for (int a = 0; a < multiPart->parts.size(); a++) {
442 QIODevice *device = multiPart->parts.at(i: a).d->bodyDevice;
443 // we are sequential if any of the bodyDevices of our parts are sequential;
444 // when reading from a byte array, we are not sequential
445 if (device && device->isSequential())
446 return true;
447 }
448 return false;
449}
450
451bool QHttpMultiPartIODevice::reset()
452{
453 // Reset QIODevice's data
454 QIODevice::reset();
455 for (int a = 0; a < multiPart->parts.size(); a++)
456 if (!multiPart->parts[a].d->reset())
457 return false;
458 readPointer = 0;
459 return true;
460}
461qint64 QHttpMultiPartIODevice::readData(char *data, qint64 maxSize)
462{
463 qint64 bytesRead = 0, index = 0;
464
465 // skip the parts we have already read
466 while (index < multiPart->parts.size() &&
467 readPointer >= partOffsets.at(i: index) + multiPart->parts.at(i: index).d->size()
468 + multiPart->boundary.size() + 6) // 6 == 2 boundary dashes, \r\n after boundary, \r\n after multipart
469 index++;
470
471 // read the data
472 while (bytesRead < maxSize && index < multiPart->parts.size()) {
473
474 // check whether we need to read the boundary of the current part
475 QByteArray boundaryData = "--" + multiPart->boundary + "\r\n";
476 qint64 boundaryCount = boundaryData.size();
477 qint64 partIndex = readPointer - partOffsets.at(i: index);
478 if (partIndex < boundaryCount) {
479 qint64 boundaryBytesRead = qMin(a: boundaryCount - partIndex, b: maxSize - bytesRead);
480 memcpy(dest: data + bytesRead, src: boundaryData.constData() + partIndex, n: boundaryBytesRead);
481 bytesRead += boundaryBytesRead;
482 readPointer += boundaryBytesRead;
483 partIndex += boundaryBytesRead;
484 }
485
486 // check whether we need to read the data of the current part
487 if (bytesRead < maxSize && partIndex >= boundaryCount && partIndex < boundaryCount + multiPart->parts.at(i: index).d->size()) {
488 qint64 dataBytesRead = multiPart->parts[index].d->readData(data: data + bytesRead, maxSize: maxSize - bytesRead);
489 if (dataBytesRead == -1)
490 return -1;
491 bytesRead += dataBytesRead;
492 readPointer += dataBytesRead;
493 partIndex += dataBytesRead;
494 }
495
496 // check whether we need to read the ending CRLF of the current part
497 if (bytesRead < maxSize && partIndex >= boundaryCount + multiPart->parts.at(i: index).d->size()) {
498 if (bytesRead == maxSize - 1)
499 return bytesRead;
500 memcpy(dest: data + bytesRead, src: "\r\n", n: 2);
501 bytesRead += 2;
502 readPointer += 2;
503 index++;
504 }
505 }
506 // check whether we need to return the final boundary
507 if (bytesRead < maxSize && index == multiPart->parts.size()) {
508 QByteArray finalBoundary = "--" + multiPart->boundary + "--\r\n";
509 qint64 boundaryIndex = readPointer + finalBoundary.size() - size();
510 qint64 lastBoundaryBytesRead = qMin(a: finalBoundary.size() - boundaryIndex, b: maxSize - bytesRead);
511 memcpy(dest: data + bytesRead, src: finalBoundary.constData() + boundaryIndex, n: lastBoundaryBytesRead);
512 bytesRead += lastBoundaryBytesRead;
513 readPointer += lastBoundaryBytesRead;
514 }
515 return bytesRead;
516}
517
518qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
519{
520 Q_UNUSED(data);
521 Q_UNUSED(maxSize);
522 return -1;
523}
524
525#ifndef QT_NO_DEBUG_STREAM
526
527/*!
528 \fn QDebug QHttpPart::operator<<(QDebug debug, const QHttpPart &part)
529
530 Writes the \a part into the \a debug object for debugging purposes.
531 Unless a device is set, the size of the body is shown.
532
533 \sa {Debugging Techniques}
534 \since 6.8
535*/
536
537QDebug operator<<(QDebug debug, const QHttpPart &part)
538{
539 const QDebugStateSaver saver(debug);
540 debug.resetFormat().nospace().noquote();
541
542 debug << "QHttpPart(headers = ["
543 << part.d->cookedHeaders
544 << "], http headers = ["
545 << part.d->httpHeaders
546 << "],";
547
548 if (part.d->bodyDevice) {
549 debug << " bodydevice = ["
550 << part.d->bodyDevice
551 << ", is open: "
552 << part.d->bodyDevice->isOpen()
553 << "]";
554 } else {
555 debug << " size of body = "
556 << part.d->body.size()
557 << " bytes";
558 }
559
560 debug << ")";
561
562 return debug;
563}
564
565#endif // QT_NO_DEBUG_STREAM
566
567
568QT_END_NAMESPACE
569
570#include "moc_qhttpmultipart.cpp"
571

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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