summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/snippets/code/src_network_access_qhttpmultipart.cpp67
-rw-r--r--doc/src/snippets/code/src_network_access_qhttppart.cpp65
-rw-r--r--src/network/access/access.pri7
-rw-r--r--src/network/access/qhttpmultipart.cpp548
-rw-r--r--src/network/access/qhttpmultipart.h119
-rw-r--r--src/network/access/qhttpmultipart_p.h182
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp93
-rw-r--r--src/network/access/qnetworkaccessmanager.h3
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h2
-rw-r--r--src/network/access/qnetworkrequest.cpp9
-rw-r--r--src/network/access/qnetworkrequest.h3
-rw-r--r--src/network/access/qnetworkrequest_p.h3
-rw-r--r--tests/auto/qnetworkreply/image1.jpgbin0 -> 1045459 bytes
-rw-r--r--tests/auto/qnetworkreply/image2.jpgbin0 -> 879218 bytes
-rw-r--r--tests/auto/qnetworkreply/image3.jpgbin0 -> 887729 bytes
-rw-r--r--tests/auto/qnetworkreply/tst_qnetworkreply.cpp343
16 files changed, 1439 insertions, 5 deletions
diff --git a/doc/src/snippets/code/src_network_access_qhttpmultipart.cpp b/doc/src/snippets/code/src_network_access_qhttpmultipart.cpp
new file mode 100644
index 0000000000..9ad2222434
--- /dev/null
+++ b/doc/src/snippets/code/src_network_access_qhttpmultipart.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! [0]
+QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+
+QHttpPart textPart;
+textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""));
+textPart.setBody("my text");
+
+QHttpPart imagePart;
+imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"image\""));
+QFile *file = new QFile("image.jpg");
+file->open(QIODevice::ReadOnly);
+imagePart.setBodyDevice(file);
+file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
+
+multiPart->append(textPart);
+multiPart->append(imagePart);
+
+QUrl url("http://my.server.tld");
+QNetworkRequest request(url);
+
+QNetworkAccessManager manager;
+QNetworkReply *reply = manager.post(request, multiPart);
+multiPart->setParent(reply); // delete the multiPart with the reply
+// here connect signals etc.
+//! [0]
diff --git a/doc/src/snippets/code/src_network_access_qhttppart.cpp b/doc/src/snippets/code/src_network_access_qhttppart.cpp
new file mode 100644
index 0000000000..0e2dbea43b
--- /dev/null
+++ b/doc/src/snippets/code/src_network_access_qhttppart.cpp
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! [0]
+Content-Type: text/plain
+Content-Disposition: form-data; name="text"
+
+here goes the body
+//! [0]
+
+//! [1]
+QHttpPart textPart;
+textPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
+textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""));
+textPart.setBody("here goes the body");
+//! [1]
+
+//! [2]
+QHttpPart imagePart;
+imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"image\""));
+imagePart.setRawHeader("Content-ID", "my@content.id"); // add any headers you like via setRawHeader()
+QFile *file = new QFile("image.jpg");
+file->open(QIODevice::ReadOnly);
+imagePart.setBodyDevice(file);
+//! [2]
+
diff --git a/src/network/access/access.pri b/src/network/access/access.pri
index 57a79b341b..5ead3ad37f 100644
--- a/src/network/access/access.pri
+++ b/src/network/access/access.pri
@@ -34,7 +34,9 @@ HEADERS += \
access/qabstractnetworkcache.h \
access/qnetworkdiskcache_p.h \
access/qnetworkdiskcache.h \
- access/qhttpthreaddelegate_p.h
+ access/qhttpthreaddelegate_p.h \
+ access/qhttpmultipart.h \
+ access/qhttpmultipart_p.h
SOURCES += \
access/qftp.cpp \
@@ -62,6 +64,7 @@ SOURCES += \
access/qnetworkreplyfileimpl.cpp \
access/qabstractnetworkcache.cpp \
access/qnetworkdiskcache.cpp \
- access/qhttpthreaddelegate.cpp
+ access/qhttpthreaddelegate.cpp \
+ access/qhttpmultipart.cpp
include($$PWD/../../3rdparty/zlib_dependency.pri)
diff --git a/src/network/access/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp
new file mode 100644
index 0000000000..d329d5c666
--- /dev/null
+++ b/src/network/access/qhttpmultipart.cpp
@@ -0,0 +1,548 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpmultipart.h"
+#include "qhttpmultipart_p.h"
+#include "QtCore/qdatetime.h" // for initializing the random number generator with QTime
+#include "QtCore/qmutex.h"
+#include "QtCore/qthreadstorage.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QHttpPart
+ \brief The QHttpPart class holds a body part to be used inside a
+ HTTP multipart MIME message.
+ \since 4.8
+
+ \ingroup network
+ \inmodule QtNetwork
+
+ The QHttpPart class holds a body part to be used inside a HTTP
+ multipart MIME message (which is represented by the QHttpMultiPart class).
+ A QHttpPart consists of a header block
+ and a data block, which are separated by each other by two
+ consecutive new lines. An example for one part would be:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 0
+
+ For setting headers, use setHeader() and setRawHeader(), which behave
+ exactly like QNetworkRequest::setHeader() and QNetworkRequest::setRawHeader().
+
+ For reading small pieces of data, use setBody(); for larger data blocks
+ like e.g. images, use setBodyDevice(). The latter method saves memory by
+ not copying the data internally, but reading directly from the device.
+ This means that the device must be opened and readable at the moment when
+ the multipart message containing the body part is sent on the network via
+ QNetworkAccessManager::post().
+
+ To construct a QHttpPart with a small body, consider the following snippet
+ (this produces the data shown in the example above):
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 1
+
+ To construct a QHttpPart reading from a device (e.g. a file), the following
+ can be applied:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 2
+
+ Be aware that QHttpPart does not take ownership of the device when set, so
+ it is the developer's responsability to destroy it when it is not needed anymore.
+ A good idea might be to set the multipart message as parent object for the device,
+ as documented at the documentation for QHttpMultiPart.
+
+ \sa QHttpMultiPart, QNetworkAccessManager
+*/
+
+
+/*!
+ Constructs an empty QHttpPart object.
+*/
+QHttpPart::QHttpPart() : d(new QHttpPartPrivate)
+{
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QHttpPart::QHttpPart(const QHttpPart &other) : d(other.d)
+{
+}
+
+/*!
+ Destroys this QHttpPart.
+*/
+QHttpPart::~QHttpPart()
+{
+ d = 0;
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QHttpPart &QHttpPart::operator=(const QHttpPart &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ Returns true if this object is the same as \a other (i.e., if they
+ have the same headers and body).
+
+ \sa operator!=()
+*/
+bool QHttpPart::operator==(const QHttpPart &other) const
+{
+ return d == other.d || *d == *other.d;
+}
+
+/*!
+ \fn bool QHttpPart::operator!=(const QHttpPart &other) const
+
+ Returns true if this object is not the same as \a other.
+
+ \sa operator==()
+*/
+
+/*!
+ Sets the value of the known header \a header to be \a value,
+ overriding any previously set headers.
+
+ \sa QNetworkRequest::KnownHeaders, setRawHeader(), QNetworkRequest::setHeader()
+*/
+void QHttpPart::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)
+{
+ d->setCookedHeader(header, value);
+}
+
+/*!
+ Sets the header \a headerName to be of value \a headerValue. If \a
+ headerName corresponds to a known header (see
+ QNetworkRequest::KnownHeaders), the raw format will be parsed and
+ the corresponding "cooked" header will be set as well.
+
+ Note: setting the same header twice overrides the previous
+ setting. To accomplish the behaviour of multiple HTTP headers of
+ the same name, you should concatenate the two values, separating
+ them with a comma (",") and set one single raw header.
+
+ \sa QNetworkRequest::KnownHeaders, setHeader(), QNetworkRequest::setRawHeader()
+*/
+void QHttpPart::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
+{
+ d->setRawHeader(headerName, headerValue);
+}
+
+/*!
+ Sets the body of this MIME part to \a body. The body set with this method
+ will be used unless the device is set via setBodyDevice(). For a large
+ amount of data (e.g. an image), use setBodyDevice(), which will not copy
+ the data internally.
+
+ \sa setBodyDevice()
+*/
+void QHttpPart::setBody(const QByteArray &body)
+{
+ d->setBody(body);
+}
+
+/*!
+ Sets the device to read the content from to \a device. 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 device must be open and readable. QHttpPart does not take ownership
+ of \a device, i.e. the device must be closed and destroyed if necessary.
+ if \a device is sequential (e.g. sockets, but not files),
+ QNetworkAccessManager::post() should be called after \a device has
+ emitted finished().
+ For unsetting the device and using data set via setBody(), use
+ "setBodyDevice(0)".
+
+ \sa setBody(), QNetworkAccessManager::post()
+ */
+void QHttpPart::setBodyDevice(QIODevice *device)
+{
+ d->setBodyDevice(device);
+}
+
+
+
+/*!
+ \class QHttpMultiPart
+ \brief The QHttpMultiPart class resembles a MIME multipart message to be sent over HTTP.
+ \since 4.8
+
+ \ingroup network
+ \inmodule QtNetwork
+
+ The QHttpMultiPart resembles a MIME multipart message, as described in RFC 2046,
+ which is to be sent over HTTP.
+ A multipart message consists of an arbitrary number of body parts (see QHttpPart),
+ which are separated by a unique boundary. The boundary of the QHttpMultiPart is
+ constructed with the string "boundary_.oOo._" followed by random characters,
+ and provides enough uniqueness to make sure it does not occur inside the parts itself.
+ If desired, the boundary can still be set via setBoundary().
+
+ As an example, consider the following code snippet, which constructs a multipart
+ message containing a text part followed by an image part:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttpmultipart.cpp 0
+
+ \sa QHttpPart, QNetworkAccessManager::post()
+*/
+
+/*!
+ \enum QHttpMultiPart::ContentType
+
+ List of known content types for a multipart subtype as described
+ in RFC 2046 and others.
+
+ \value MixedType corresponds to the "multipart/mixed" subtype,
+ meaning the body parts are independent of each other, as described
+ in RFC 2046.
+
+ \value RelatedType corresponds to the "multipart/related" subtype,
+ meaning the body parts are related to each other, as described in RFC 2387.
+
+ \value FormDataType corresponds to the "multipart/form-data"
+ subtype, meaning the body parts contain form elements, as described in RFC 2388.
+
+ \value AlternativeType corresponds to the "multipart/alternative"
+ subtype, meaning the body parts are alternative representations of
+ the same information, as described in RFC 2046.
+
+ \sa setContentType()
+*/
+
+/*!
+ Constructs a QHttpMultiPart with content type MixedType and sets
+ parent as the parent object.
+
+ \sa QHttpMultiPart::ContentType
+*/
+QHttpMultiPart::QHttpMultiPart(QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
+{
+ Q_D(QHttpMultiPart);
+ d->contentType = MixedType;
+}
+
+/*!
+ Constructs a QHttpMultiPart with content type \a contentType and
+ sets parent as the parent object.
+
+ \sa QHttpMultiPart::ContentType
+*/
+QHttpMultiPart::QHttpMultiPart(QHttpMultiPart::ContentType contentType, QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
+{
+ Q_D(QHttpMultiPart);
+ d->contentType = contentType;
+}
+
+/*!
+ Destroys the multipart.
+*/
+QHttpMultiPart::~QHttpMultiPart()
+{
+}
+
+/*!
+ Appends \a httpPart to this multipart.
+*/
+void QHttpMultiPart::append(const QHttpPart &httpPart)
+{
+ d_func()->parts.append(httpPart);
+}
+
+/*!
+ Sets the content type to \a contentType. The content type will be used
+ in the HTTP header section when sending the multipart message via
+ QNetworkAccessManager::post().
+ In case you want to use a multipart subtype not contained in
+ QHttpMultiPart::ContentType,
+ you can add the "Content-Type" header field to the QNetworkRequest
+ by hand, and then use this request together with the multipart
+ message for posting.
+
+ \sa QHttpMultiPart::ContentType, QNetworkAccessManager::post()
+*/
+void QHttpMultiPart::setContentType(QHttpMultiPart::ContentType contentType)
+{
+ d_func()->contentType = contentType;
+}
+
+/*!
+ returns the boundary.
+
+ \sa setBoundary()
+*/
+QByteArray QHttpMultiPart::boundary() const
+{
+ return d_func()->boundary;
+}
+
+/*!
+ Sets the boundary to \a boundary.
+
+ Usually, you do not need to generate a boundary yourself; upon construction
+ the boundary is initiated with the string "boundary_.oOo._" followed by random
+ characters, and provides enough uniqueness to make sure it does not occur
+ inside the parts itself.
+
+ \sa boundary()
+*/
+void QHttpMultiPart::setBoundary(const QByteArray &boundary)
+{
+ d_func()->boundary = boundary;
+}
+
+
+
+// ------------------------------------------------------------------
+// ----------- implementations of private classes: ------------------
+// ------------------------------------------------------------------
+
+
+
+qint64 QHttpPartPrivate::bytesAvailable() const
+{
+ checkHeaderCreated();
+ qint64 bytesAvailable = header.count();
+ if (bodyDevice) {
+ bytesAvailable += bodyDevice->bytesAvailable() - readPointer;
+ } else {
+ bytesAvailable += body.count() - readPointer;
+ }
+ // the device might have closed etc., so make sure we do not return a negative value
+ return qMax(bytesAvailable, (qint64) 0);
+}
+
+qint64 QHttpPartPrivate::readData(char *data, qint64 maxSize)
+{
+ checkHeaderCreated();
+ qint64 bytesRead = 0;
+ qint64 headerDataCount = header.count();
+
+ // read header if it has not been read yet
+ if (readPointer < headerDataCount) {
+ bytesRead = qMin(headerDataCount - readPointer, maxSize);
+ const char *headerData = header.constData();
+ memcpy(data, headerData + readPointer, bytesRead);
+ readPointer += bytesRead;
+ }
+ // read content if there is still space
+ if (bytesRead < maxSize) {
+ if (bodyDevice) {
+ qint64 dataBytesRead = bodyDevice->read(data + bytesRead, maxSize - bytesRead);
+ if (dataBytesRead == -1)
+ return -1;
+ bytesRead += dataBytesRead;
+ readPointer += dataBytesRead;
+ } else {
+ qint64 contentBytesRead = qMin(body.count() - readPointer + headerDataCount, maxSize - bytesRead);
+ const char *contentData = body.constData();
+ // if this method is called several times, we need to find the
+ // right offset in the content ourselves:
+ memcpy(data + bytesRead, contentData + readPointer - headerDataCount, contentBytesRead);
+ bytesRead += contentBytesRead;
+ readPointer += contentBytesRead;
+ }
+ }
+ return bytesRead;
+}
+
+qint64 QHttpPartPrivate::size() const
+{
+ checkHeaderCreated();
+ qint64 size = header.count();
+ if (bodyDevice) {
+ size += bodyDevice->size();
+ } else {
+ size += body.count();
+ }
+ return size;
+}
+
+bool QHttpPartPrivate::reset()
+{
+ bool ret = true;
+ if (bodyDevice)
+ if (!bodyDevice->reset())
+ ret = false;
+ readPointer = 0;
+ return ret;
+}
+void QHttpPartPrivate::checkHeaderCreated() const
+{
+ if (!headerCreated) {
+ // copied from QHttpNetworkRequestPrivate::header() and adapted
+ QList<QPair<QByteArray, QByteArray> > fields = allRawHeaders();
+ QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
+ for (; it != fields.constEnd(); ++it)
+ header += it->first + ": " + it->second + "\r\n";
+ header += "\r\n";
+ headerCreated = true;
+ }
+}
+
+Q_GLOBAL_STATIC(QThreadStorage<bool *>, seedCreatedStorage);
+
+QHttpMultiPartPrivate::QHttpMultiPartPrivate() : contentType(QHttpMultiPart::MixedType), device(new QHttpMultiPartIODevice(this))
+{
+ if (!seedCreatedStorage()->hasLocalData()) {
+ qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) ^ reinterpret_cast<quintptr>(this));
+ seedCreatedStorage()->setLocalData(new bool(true));
+ }
+
+ boundary = QByteArray("boundary_.oOo._")
+ + QByteArray::number(qrand()).toBase64()
+ + QByteArray::number(qrand()).toBase64()
+ + QByteArray::number(qrand()).toBase64();
+
+ // boundary must not be longer than 70 characters, see RFC 2046, section 5.1.1
+ if (boundary.count() > 70)
+ boundary = boundary.left(70);
+}
+
+qint64 QHttpMultiPartIODevice::size() const
+{
+ // if not done yet, we calculate the size and the offsets of each part,
+ // including boundary (needed later in readData)
+ if (deviceSize == -1) {
+ qint64 currentSize = 0;
+ qint64 boundaryCount = multiPart->boundary.count();
+ for (int a = 0; a < multiPart->parts.count(); a++) {
+ partOffsets.append(currentSize);
+ // 4 additional bytes for the "--" before and the "\r\n" after the boundary,
+ // and 2 bytes for the "\r\n" after the content
+ currentSize += boundaryCount + 4 + multiPart->parts.at(a).d->size() + 2;
+ }
+ currentSize += boundaryCount + 4; // size for ending boundary and 2 beginning and ending dashes
+ deviceSize = currentSize;
+ }
+ return deviceSize;
+}
+
+bool QHttpMultiPartIODevice::isSequential() const
+{
+ for (int a = 0; a < multiPart->parts.count(); a++) {
+ QIODevice *device = multiPart->parts.at(a).d->bodyDevice;
+ // we are sequential if any of the bodyDevices of our parts are sequential;
+ // when reading from a byte array, we are not sequential
+ if (device && device->isSequential())
+ return true;
+ }
+ return false;
+}
+
+bool QHttpMultiPartIODevice::reset()
+{
+ for (int a = 0; a < multiPart->parts.count(); a++)
+ if (!multiPart->parts[a].d->reset())
+ return false;
+ return true;
+}
+qint64 QHttpMultiPartIODevice::readData(char *data, qint64 maxSize)
+{
+ qint64 bytesRead = 0, index = 0;
+
+ // skip the parts we have already read
+ while (index < multiPart->parts.count() &&
+ readPointer >= partOffsets.at(index) + multiPart->parts.at(index).d->size())
+ index++;
+
+ // read the data
+ while (bytesRead < maxSize && index < multiPart->parts.count()) {
+
+ // check whether we need to read the boundary of the current part
+ QByteArray boundaryData = "--" + multiPart->boundary + "\r\n";
+ qint64 boundaryCount = boundaryData.count();
+ qint64 partIndex = readPointer - partOffsets.at(index);
+ if (partIndex < boundaryCount) {
+ qint64 boundaryBytesRead = qMin(boundaryCount - partIndex, maxSize - bytesRead);
+ memcpy(data + bytesRead, boundaryData.constData() + partIndex, boundaryBytesRead);
+ bytesRead += boundaryBytesRead;
+ readPointer += boundaryBytesRead;
+ partIndex += boundaryBytesRead;
+ }
+
+ // check whether we need to read the data of the current part
+ if (bytesRead < maxSize && partIndex >= boundaryCount && partIndex < boundaryCount + multiPart->parts.at(index).d->size()) {
+ qint64 dataBytesRead = multiPart->parts[index].d->readData(data + bytesRead, maxSize - bytesRead);
+ if (dataBytesRead == -1)
+ return -1;
+ bytesRead += dataBytesRead;
+ readPointer += dataBytesRead;
+ partIndex += dataBytesRead;
+ }
+
+ // check whether we need to read the ending CRLF of the current part
+ if (bytesRead < maxSize && partIndex >= boundaryCount + multiPart->parts.at(index).d->size()) {
+ if (bytesRead == maxSize - 1)
+ return bytesRead;
+ memcpy(data + bytesRead, "\r\n", 2);
+ bytesRead += 2;
+ readPointer += 2;
+ index++;
+ }
+ }
+ // check whether we need to return the final boundary
+ if (bytesRead < maxSize && index == multiPart->parts.count()) {
+ QByteArray finalBoundary = "--" + multiPart->boundary + "--";
+ qint64 boundaryIndex = readPointer + finalBoundary.count() - size();
+ qint64 lastBoundaryBytesRead = qMin(finalBoundary.count() - boundaryIndex, maxSize - bytesRead);
+ memcpy(data + bytesRead, finalBoundary.constData() + boundaryIndex, lastBoundaryBytesRead);
+ bytesRead += lastBoundaryBytesRead;
+ readPointer += lastBoundaryBytesRead;
+ }
+ return bytesRead;
+}
+
+qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(maxSize);
+ return -1;
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpmultipart.h b/src/network/access/qhttpmultipart.h
new file mode 100644
index 0000000000..0a3342c6bb
--- /dev/null
+++ b/src/network/access/qhttpmultipart.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPMULTIPART_H
+#define QHTTPMULTIPART_H
+
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QByteArray>
+#include <QtNetwork/QNetworkRequest>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Network)
+
+class QHttpPartPrivate;
+class QHttpMultiPart;
+
+class Q_NETWORK_EXPORT QHttpPart
+{
+public:
+ QHttpPart();
+ QHttpPart(const QHttpPart &other);
+ ~QHttpPart();
+ QHttpPart &operator=(const QHttpPart &other);
+ bool operator==(const QHttpPart &other) const;
+ inline bool operator!=(const QHttpPart &other) const
+ { return !operator==(other); }
+
+ void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
+ void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue);
+
+ void setBody(const QByteArray &body);
+ void setBodyDevice(QIODevice *device);
+
+private:
+ QSharedDataPointer<QHttpPartPrivate> d;
+
+ friend class QHttpMultiPartIODevice;
+};
+
+class QHttpMultiPartPrivate;
+
+class Q_NETWORK_EXPORT QHttpMultiPart : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum ContentType {
+ MixedType,
+ RelatedType,
+ FormDataType,
+ AlternativeType
+ };
+
+ QHttpMultiPart(QObject *parent = 0);
+ QHttpMultiPart(ContentType contentType, QObject *parent = 0);
+ ~QHttpMultiPart();
+
+ void append(const QHttpPart &httpPart);
+
+ void setContentType(ContentType contentType);
+
+ QByteArray boundary() const;
+ void setBoundary(const QByteArray &boundary);
+
+private:
+ Q_DECLARE_PRIVATE(QHttpMultiPart)
+ Q_DISABLE_COPY(QHttpMultiPart)
+
+ friend class QNetworkAccessManager;
+ friend class QNetworkAccessManagerPrivate;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QHTTPMULTIPART_H
diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h
new file mode 100644
index 0000000000..7dc13e9a61
--- /dev/null
+++ b/src/network/access/qhttpmultipart_p.h
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPMULTIPART_P_H
+#define QHTTPMULTIPART_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtCore/qshareddata.h"
+#include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate
+#include "private/qobject_p.h"
+
+QT_BEGIN_NAMESPACE
+
+
+class QHttpPartPrivate: public QSharedData, public QNetworkHeadersPrivate
+{
+public:
+ inline QHttpPartPrivate() : bodyDevice(0), headerCreated(false), readPointer(0)
+ {
+ }
+ ~QHttpPartPrivate()
+ {
+ }
+
+
+ QHttpPartPrivate(const QHttpPartPrivate &other)
+ : QSharedData(other), QNetworkHeadersPrivate(other), body(other.body),
+ header(other.header), headerCreated(other.headerCreated), readPointer(other.readPointer)
+ {
+ bodyDevice = other.bodyDevice;
+ }
+
+ inline bool operator==(const QHttpPartPrivate &other) const
+ {
+ return rawHeaders == other.rawHeaders && body == other.body &&
+ bodyDevice == other.bodyDevice && readPointer == other.readPointer;
+ }
+
+ void setBodyDevice(QIODevice *device) {
+ bodyDevice = device;
+ readPointer = 0;
+ }
+ void setBody(const QByteArray &newBody) {
+ body = newBody;
+ readPointer = 0;
+ }
+
+ // QIODevice-style methods called by QHttpMultiPartIODevice (but this class is
+ // not a QIODevice):
+ qint64 bytesAvailable() const;
+ qint64 readData(char *data, qint64 maxSize);
+ qint64 size() const;
+ bool reset();
+
+ QByteArray body;
+ QIODevice *bodyDevice;
+
+private:
+ void checkHeaderCreated() const;
+
+ mutable QByteArray header;
+ mutable bool headerCreated;
+ qint64 readPointer;
+};
+
+
+
+class QHttpMultiPartPrivate;
+
+class Q_AUTOTEST_EXPORT QHttpMultiPartIODevice : public QIODevice
+{
+public:
+ QHttpMultiPartIODevice(QHttpMultiPartPrivate *parentMultiPart) :
+ QIODevice(), multiPart(parentMultiPart), readPointer(0), deviceSize(-1) {
+ }
+
+ ~QHttpMultiPartIODevice() {
+ }
+
+ virtual bool atEnd() const {
+ return readPointer == size();
+ }
+
+ virtual qint64 bytesAvailable() const {
+ return size() - readPointer;
+ }
+
+ virtual void close() {
+ readPointer = 0;
+ partOffsets.clear();
+ deviceSize = -1;
+ QIODevice::close();
+ }
+
+ virtual qint64 bytesToWrite() const {
+ return 0;
+ }
+
+ virtual qint64 size() const;
+ virtual bool isSequential() const;
+ virtual bool reset();
+ virtual qint64 readData(char *data, qint64 maxSize);
+ virtual qint64 writeData(const char *data, qint64 maxSize);
+
+ QHttpMultiPartPrivate *multiPart;
+ qint64 readPointer;
+ mutable QList<qint64> partOffsets;
+ mutable qint64 deviceSize;
+};
+
+
+
+class QHttpMultiPartPrivate: public QObjectPrivate
+{
+public:
+
+ QHttpMultiPartPrivate();
+
+ ~QHttpMultiPartPrivate()
+ {
+ delete device;
+ }
+
+ QList<QHttpPart> parts;
+ QByteArray boundary;
+ QHttpMultiPart::ContentType contentType;
+ QHttpMultiPartIODevice *device;
+
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QHTTPMULTIPART_P_H
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 7e75045da6..0337607892 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -64,6 +64,8 @@
#include "QtNetwork/qauthenticator.h"
#include "QtNetwork/qsslconfiguration.h"
#include "QtNetwork/qnetworkconfigmanager.h"
+#include "QtNetwork/qhttpmultipart.h"
+#include "qhttpmultipart_p.h"
#include "qthread.h"
@@ -629,6 +631,46 @@ QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, const
}
/*!
+ \since 4.8
+
+ \overload
+
+ Sends the contents of the \a multiPart message to the destination
+ specified by \a request.
+
+ This can be used for sending MIME multipart messages over HTTP.
+
+ \sa QHttpMultiPart, QHttpPart, put()
+*/
+QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
+ QIODevice *device = multiPart->d_func()->device;
+ QNetworkReply *reply = post(newRequest, device);
+ return reply;
+}
+
+/*!
+ \since 4.8
+
+ \overload
+
+ Sends the contents of the \a multiPart message to the destination
+ specified by \a request.
+
+ This can be used for sending MIME multipart messages over HTTP.
+
+ \sa QHttpMultiPart, QHttpPart, post()
+*/
+QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
+ QIODevice *device = multiPart->d_func()->device;
+ QNetworkReply *reply = put(newRequest, device);
+ return reply;
+}
+
+/*!
Uploads the contents of \a data to the destination \a request and
returnes a new QNetworkReply object that will be open for reply.
@@ -654,7 +696,8 @@ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QIODev
/*!
\overload
- Sends the contents of the \a data byte array to the destination
+
+ Sends the contents of the \a data byte array to the destination
specified by \a request.
*/
QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, const QByteArray &data)
@@ -1177,6 +1220,54 @@ void QNetworkAccessManagerPrivate::_q_networkSessionStateChanged(QNetworkSession
}
#endif // QT_NO_BEARERMANAGEMENT
+QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ // copy the request, we probably need to add some headers
+ QNetworkRequest newRequest(request);
+
+ // add Content-Type header if not there already
+ if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
+ QByteArray contentType;
+ contentType.reserve(34 + multiPart->d_func()->boundary.count());
+ contentType += "multipart/";
+ switch (multiPart->d_func()->contentType) {
+ case QHttpMultiPart::RelatedType:
+ contentType += "related";
+ break;
+ case QHttpMultiPart::FormDataType:
+ contentType += "form-data";
+ break;
+ case QHttpMultiPart::AlternativeType:
+ contentType += "alternative";
+ break;
+ default:
+ contentType += "mixed";
+ break;
+ }
+ // putting the boundary into quotes, recommended in RFC 2046 section 5.1.1
+ contentType += "; boundary=\"" + multiPart->d_func()->boundary + "\"";
+ newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
+ }
+
+ // add MIME-Version header if not there already (we must include the header
+ // if the message conforms to RFC 2045, see section 4 of that RFC)
+ QByteArray mimeHeader("MIME-Version");
+ if (!request.hasRawHeader(mimeHeader))
+ newRequest.setRawHeader(mimeHeader, QByteArray("1.0"));
+
+ QIODevice *device = multiPart->d_func()->device;
+ if (!device->isReadable()) {
+ if (!device->isOpen()) {
+ if (!device->open(QIODevice::ReadOnly))
+ qWarning("could not open device for reading");
+ } else {
+ qWarning("device is not readable");
+ }
+ }
+
+ return newRequest;
+}
+
QT_END_NAMESPACE
#include "moc_qnetworkaccessmanager.cpp"
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index d67b8ace38..47760b210b 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -65,6 +65,7 @@ class QSslError;
#if !defined(QT_NO_BEARERMANAGEMENT) && !defined(QT_MOBILITY_BEARER)
class QNetworkConfiguration;
#endif
+class QHttpMultiPart;
class QNetworkReplyImplPrivate;
class QNetworkAccessManagerPrivate;
@@ -116,8 +117,10 @@ public:
QNetworkReply *get(const QNetworkRequest &request);
QNetworkReply *post(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
+ QNetworkReply *post(const QNetworkRequest &request, QHttpMultiPart *multiPart);
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data);
+ QNetworkReply *put(const QNetworkRequest &request, QHttpMultiPart *multiPart);
QNetworkReply *deleteResource(const QNetworkRequest &request);
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = 0);
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
index ee6ad70f60..0f182211ed 100644
--- a/src/network/access/qnetworkaccessmanager_p.h
+++ b/src/network/access/qnetworkaccessmanager_p.h
@@ -119,6 +119,8 @@ public:
void _q_networkSessionStateChanged(QNetworkSession::State state);
#endif
+ QNetworkRequest prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart);
+
// this is the cache for storing downloaded files
QAbstractNetworkCache *networkCache;
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index a48a26f77f..665ee28283 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -639,6 +639,9 @@ static QByteArray headerName(QNetworkRequest::KnownHeaders header)
case QNetworkRequest::SetCookieHeader:
return "Set-Cookie";
+ case QNetworkRequest::ContentDispositionHeader:
+ return "Content-Disposition";
+
// no default:
// if new values are added, this will generate a compiler warning
}
@@ -651,6 +654,7 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria
switch (header) {
case QNetworkRequest::ContentTypeHeader:
case QNetworkRequest::ContentLengthHeader:
+ case QNetworkRequest::ContentDispositionHeader:
return value.toByteArray();
case QNetworkRequest::LocationHeader:
@@ -812,6 +816,11 @@ QNetworkHeadersPrivate::findRawHeader(const QByteArray &key) const
return end; // not found
}
+QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::allRawHeaders() const
+{
+ return rawHeaders;
+}
+
QList<QByteArray> QNetworkHeadersPrivate::rawHeadersKeys() const
{
QList<QByteArray> result;
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index b5ef109a7d..d3bbba770c 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -65,7 +65,8 @@ public:
LocationHeader,
LastModifiedHeader,
CookieHeader,
- SetCookieHeader
+ SetCookieHeader,
+ ContentDispositionHeader // added for QMultipartMessage
};
enum Attribute {
HttpStatusCodeAttribute,
diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h
index 23705f5473..ea8c56f947 100644
--- a/src/network/access/qnetworkrequest_p.h
+++ b/src/network/access/qnetworkrequest_p.h
@@ -62,7 +62,7 @@
QT_BEGIN_NAMESPACE
-// this is the common part between QNetworkRequestPrivate and QNetworkReplyPrivate
+// this is the common part between QNetworkRequestPrivate, QNetworkReplyPrivate and QHttpPartPrivate
class QNetworkHeadersPrivate
{
public:
@@ -77,6 +77,7 @@ public:
QWeakPointer<QObject> originatingObject;
RawHeadersList::ConstIterator findRawHeader(const QByteArray &key) const;
+ RawHeadersList allRawHeaders() const;
QList<QByteArray> rawHeadersKeys() const;
void setRawHeader(const QByteArray &key, const QByteArray &value);
void setAllRawHeaders(const RawHeadersList &list);
diff --git a/tests/auto/qnetworkreply/image1.jpg b/tests/auto/qnetworkreply/image1.jpg
new file mode 100644
index 0000000000..dba31c1901
--- /dev/null
+++ b/tests/auto/qnetworkreply/image1.jpg
Binary files differ
diff --git a/tests/auto/qnetworkreply/image2.jpg b/tests/auto/qnetworkreply/image2.jpg
new file mode 100644
index 0000000000..72936e2de6
--- /dev/null
+++ b/tests/auto/qnetworkreply/image2.jpg
Binary files differ
diff --git a/tests/auto/qnetworkreply/image3.jpg b/tests/auto/qnetworkreply/image3.jpg
new file mode 100644
index 0000000000..cede519938
--- /dev/null
+++ b/tests/auto/qnetworkreply/image3.jpg
Binary files differ
diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
index 48b3ecc8e7..6ed8f16a08 100644
--- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp
@@ -60,6 +60,8 @@
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qnetworkcookie.h>
+#include <QtNetwork/QHttpPart>
+#include <QtNetwork/QHttpMultiPart>
#ifndef QT_NO_OPENSSL
#include <QtNetwork/qsslerror.h>
#include <QtNetwork/qsslconfiguration.h>
@@ -83,6 +85,8 @@ Q_DECLARE_METATYPE(QNetworkProxyQuery)
Q_DECLARE_METATYPE(QList<QNetworkProxy>)
Q_DECLARE_METATYPE(QNetworkReply::NetworkError)
Q_DECLARE_METATYPE(QBuffer*)
+Q_DECLARE_METATYPE(QHttpMultiPart *)
+Q_DECLARE_METATYPE(QList<QFile*>) // for multiparts
class QNetworkReplyPtr: public QSharedPointer<QNetworkReply>
{
@@ -139,6 +143,8 @@ public:
~tst_QNetworkReply();
QString runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
QNetworkReplyPtr &reply, const QByteArray &data = QByteArray());
+ QString runMultipartRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply,
+ QHttpMultiPart *multiPart, const QByteArray &verb);
QString runCustomRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply,
const QByteArray &verb, QIODevice *data);
@@ -185,10 +191,14 @@ private Q_SLOTS:
void putToHttp();
void putToHttpSynchronous_data();
void putToHttpSynchronous();
+ void putToHttpMultipart_data();
+ void putToHttpMultipart();
void postToHttp_data();
void postToHttp();
void postToHttpSynchronous_data();
void postToHttpSynchronous();
+ void postToHttpMultipart_data();
+ void postToHttpMultipart();
void deleteFromHttp_data();
void deleteFromHttp();
void putGetDeleteGetFromHttp_data();
@@ -1092,6 +1102,38 @@ void tst_QNetworkReply::storeSslConfiguration()
}
#endif
+QString tst_QNetworkReply::runMultipartRequest(const QNetworkRequest &request,
+ QNetworkReplyPtr &reply,
+ QHttpMultiPart *multiPart,
+ const QByteArray &verb)
+{
+ if (verb == "POST")
+ reply = manager.post(request, multiPart);
+ else
+ reply = manager.put(request, multiPart);
+
+ // the code below is copied from tst_QNetworkReply::runSimpleRequest, see below
+ reply->setParent(this);
+ connect(reply, SIGNAL(finished()), SLOT(finished()));
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError()));
+ multiPart->setParent(reply);
+
+ returnCode = Timeout;
+ loop = new QEventLoop;
+ QTimer::singleShot(25000, loop, SLOT(quit()));
+ int code = returnCode == Timeout ? loop->exec() : returnCode;
+ delete loop;
+ loop = 0;
+
+ switch (code) {
+ case Failure:
+ return "Request failed: " + reply->errorString();
+ case Timeout:
+ return "Network timeout";
+ }
+ return QString();
+}
+
QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op,
const QNetworkRequest &request,
QNetworkReplyPtr &reply,
@@ -1837,6 +1879,307 @@ void tst_QNetworkReply::postToHttpSynchronous()
QCOMPARE(uploadedData, md5sum.toHex());
}
+void tst_QNetworkReply::postToHttpMultipart_data()
+{
+ QTest::addColumn<QUrl>("url");
+ QTest::addColumn<QHttpMultiPart *>("multiPart");
+ QTest::addColumn<QByteArray>("expectedReplyData");
+ QTest::addColumn<QByteArray>("contentType");
+
+ QUrl url("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/multipart.cgi");
+ QByteArray expectedData;
+
+
+ // empty parts
+
+ QHttpMultiPart *emptyMultiPart = new QHttpMultiPart;
+ QTest::newRow("empty") << url << emptyMultiPart << expectedData << QByteArray("mixed");
+
+ QHttpMultiPart *emptyRelatedMultiPart = new QHttpMultiPart;
+ emptyRelatedMultiPart->setContentType(QHttpMultiPart::RelatedType);
+ QTest::newRow("empty-related") << url << emptyRelatedMultiPart << expectedData << QByteArray("related");
+
+ QHttpMultiPart *emptyAlternativeMultiPart = new QHttpMultiPart;
+ emptyAlternativeMultiPart->setContentType(QHttpMultiPart::AlternativeType);
+ QTest::newRow("empty-alternative") << url << emptyAlternativeMultiPart << expectedData << QByteArray("alternative");
+
+
+ // text-only parts
+
+ QHttpPart textPart;
+ textPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
+ textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""));
+ textPart.setBody("7 bytes");
+ QHttpMultiPart *multiPart1 = new QHttpMultiPart;
+ multiPart1->setContentType(QHttpMultiPart::FormDataType);
+ multiPart1->append(textPart);
+ expectedData = "key: text, value: 7 bytes\n";
+ QTest::newRow("text") << url << multiPart1 << expectedData << QByteArray("form-data");
+
+ QHttpMultiPart *customMultiPart = new QHttpMultiPart;
+ customMultiPart->append(textPart);
+ expectedData = "header: Content-Type, value: 'text/plain'\n"
+ "header: Content-Disposition, value: 'form-data; name=\"text\"'\n"
+ "content: 7 bytes\n"
+ "\n";
+ QTest::newRow("text-custom") << url << customMultiPart << expectedData << QByteArray("custom");
+
+ QHttpPart textPart2;
+ textPart2.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
+ textPart2.setRawHeader("myRawHeader", "myValue");
+ textPart2.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text2\""));
+ textPart2.setBody("some more bytes");
+ textPart2.setBodyDevice((QIODevice *) 1); // test whether setting and unsetting of the device works
+ textPart2.setBodyDevice(0);
+ QHttpMultiPart *multiPart2 = new QHttpMultiPart;
+ multiPart2->setContentType(QHttpMultiPart::FormDataType);
+ multiPart2->append(textPart);
+ multiPart2->append(textPart2);
+ expectedData = "key: text2, value: some more bytes\n"
+ "key: text, value: 7 bytes\n";
+ QTest::newRow("text-text") << url << multiPart2 << expectedData << QByteArray("form-data");
+
+
+ QHttpPart textPart3;
+ textPart3.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
+ textPart3.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text3\""));
+ textPart3.setRawHeader("Content-Location", "http://my.test.location.tld");
+ textPart3.setBody("even more bytes");
+ QHttpMultiPart *multiPart3 = new QHttpMultiPart;
+ multiPart3->setContentType(QHttpMultiPart::AlternativeType);
+ multiPart3->append(textPart);
+ multiPart3->append(textPart2);
+ multiPart3->append(textPart3);
+ expectedData = "header: Content-Type, value: 'text/plain'\n"
+ "header: Content-Disposition, value: 'form-data; name=\"text\"'\n"
+ "content: 7 bytes\n"
+ "\n"
+ "header: Content-Type, value: 'text/plain'\n"
+ "header: myRawHeader, value: 'myValue'\n"
+ "header: Content-Disposition, value: 'form-data; name=\"text2\"'\n"
+ "content: some more bytes\n"
+ "\n"
+ "header: Content-Type, value: 'text/plain'\n"
+ "header: Content-Disposition, value: 'form-data; name=\"text3\"'\n"
+ "header: Content-Location, value: 'http://my.test.location.tld'\n"
+ "content: even more bytes\n\n";
+ QTest::newRow("text-text-text") << url << multiPart3 << expectedData << QByteArray("alternative");
+
+
+
+ // text and image parts
+
+ QHttpPart imagePart11;
+ imagePart11.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart11.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage\""));
+ imagePart11.setRawHeader("Content-Location", "http://my.test.location.tld");
+ imagePart11.setRawHeader("Content-ID", "my@id.tld");
+ QFile *file11 = new QFile(SRCDIR "/image1.jpg");
+ file11->open(QIODevice::ReadOnly);
+ imagePart11.setBodyDevice(file11);
+ QHttpMultiPart *imageMultiPart1 = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+ imageMultiPart1->append(imagePart11);
+ file11->setParent(imageMultiPart1);
+ expectedData = "key: testImage, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"; // md5 sum of file
+ QTest::newRow("image") << url << imageMultiPart1 << expectedData << QByteArray("form-data");
+
+ QHttpPart imagePart21;
+ imagePart21.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart21.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\""));
+ imagePart21.setRawHeader("Content-Location", "http://my.test.location.tld");
+ imagePart21.setRawHeader("Content-ID", "my@id.tld");
+ QFile *file21 = new QFile(SRCDIR "/image1.jpg");
+ file21->open(QIODevice::ReadOnly);
+ imagePart21.setBodyDevice(file21);
+ QHttpMultiPart *imageMultiPart2 = new QHttpMultiPart();
+ imageMultiPart2->setContentType(QHttpMultiPart::FormDataType);
+ imageMultiPart2->append(textPart);
+ imageMultiPart2->append(imagePart21);
+ file21->setParent(imageMultiPart2);
+ QHttpPart imagePart22;
+ imagePart22.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart22.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\""));
+ QFile *file22 = new QFile(SRCDIR "/image2.jpg");
+ file22->open(QIODevice::ReadOnly);
+ imagePart22.setBodyDevice(file22);
+ imageMultiPart2->append(imagePart22);
+ file22->setParent(imageMultiPart2);
+ expectedData = "key: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"
+ "key: text, value: 7 bytes\n"
+ "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n";
+ QTest::newRow("text-image-image") << url << imageMultiPart2 << expectedData << QByteArray("form-data");
+
+
+ QHttpPart imagePart31;
+ imagePart31.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart31.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\""));
+ imagePart31.setRawHeader("Content-Location", "http://my.test.location.tld");
+ imagePart31.setRawHeader("Content-ID", "my@id.tld");
+ QFile *file31 = new QFile(SRCDIR "/image1.jpg");
+ file31->open(QIODevice::ReadOnly);
+ imagePart31.setBodyDevice(file31);
+ QHttpMultiPart *imageMultiPart3 = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+ imageMultiPart3->append(imagePart31);
+ file31->setParent(imageMultiPart3);
+ QHttpPart imagePart32;
+ imagePart32.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart32.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\""));
+ QFile *file32 = new QFile(SRCDIR "/image2.jpg");
+ file32->open(QIODevice::ReadOnly);
+ imagePart32.setBodyDevice(file31); // check that resetting works
+ imagePart32.setBodyDevice(file32);
+ imageMultiPart3->append(imagePart32);
+ file32->setParent(imageMultiPart3);
+ QHttpPart imagePart33;
+ imagePart33.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart33.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage3\""));
+ QFile *file33 = new QFile(SRCDIR "/image3.jpg");
+ file33->open(QIODevice::ReadOnly);
+ imagePart33.setBodyDevice(file33);
+ imageMultiPart3->append(imagePart33);
+ file33->setParent(imageMultiPart3);
+ expectedData = "key: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"
+ "key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n"
+ "key: testImage3, value: ab0eb6fd4fcf8b4436254870b4513033\n";
+ QTest::newRow("3-images") << url << imageMultiPart3 << expectedData << QByteArray("form-data");
+
+
+ // note: nesting multiparts is not working currently; for that, the outputDevice would need to be public
+
+// QHttpPart imagePart41;
+// imagePart41.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+// QFile *file41 = new QFile(SRCDIR "/image1.jpg");
+// file41->open(QIODevice::ReadOnly);
+// imagePart41.setBodyDevice(file41);
+//
+// QHttpMultiPart *innerMultiPart = new QHttpMultiPart();
+// innerMultiPart->setContentType(QHttpMultiPart::FormDataType);
+// textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant());
+// innerMultiPart->append(textPart);
+// innerMultiPart->append(imagePart41);
+// textPart2.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant());
+// innerMultiPart->append(textPart2);
+//
+// QHttpPart nestedPart;
+// nestedPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"nestedMessage"));
+// nestedPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("multipart/alternative; boundary=\"" + innerMultiPart->boundary() + "\""));
+// innerMultiPart->outputDevice()->open(QIODevice::ReadOnly);
+// nestedPart.setBodyDevice(innerMultiPart->outputDevice());
+//
+// QHttpMultiPart *outerMultiPart = new QHttpMultiPart;
+// outerMultiPart->setContentType(QHttpMultiPart::FormDataType);
+// outerMultiPart->append(textPart);
+// outerMultiPart->append(nestedPart);
+// outerMultiPart->append(textPart2);
+// expectedData = "nothing"; // the CGI.pm module running on the test server does not understand nested multiparts
+// openFiles.clear();
+// openFiles << file41;
+// QTest::newRow("nested") << url << outerMultiPart << expectedData << openFiles;
+
+
+ // test setting large chunks of content with a byte array instead of a device (DISCOURAGED because of high memory consumption,
+ // but we need to test that the behavior is correct)
+ QHttpPart imagePart51;
+ imagePart51.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
+ imagePart51.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage\""));
+ QFile *file51 = new QFile(SRCDIR "/image1.jpg");
+ file51->open(QIODevice::ReadOnly);
+ QByteArray imageData = file51->readAll();
+ file51->close();
+ delete file51;
+ imagePart51.setBody("7 bytes"); // check that resetting works
+ imagePart51.setBody(imageData);
+ QHttpMultiPart *imageMultiPart5 = new QHttpMultiPart;
+ imageMultiPart5->setContentType(QHttpMultiPart::FormDataType);
+ imageMultiPart5->append(imagePart51);
+ expectedData = "key: testImage, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"; // md5 sum of file
+ QTest::newRow("image-as-content") << url << imageMultiPart5 << expectedData << QByteArray("form-data");
+}
+
+void tst_QNetworkReply::postToHttpMultipart()
+{
+ QFETCH(QUrl, url);
+
+ static QSet<QByteArray> boundaries;
+
+ QNetworkRequest request(url);
+ QNetworkReplyPtr reply;
+
+ QFETCH(QHttpMultiPart *, multiPart);
+ QFETCH(QByteArray, expectedReplyData);
+ QFETCH(QByteArray, contentType);
+
+ // hack for testing the setting of the content-type header by hand:
+ if (contentType == "custom") {
+ QByteArray contentType("multipart/custom; boundary=\"" + multiPart->boundary() + "\"");
+ request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);
+ }
+
+ QVERIFY2(! boundaries.contains(multiPart->boundary()), "boundary '" + multiPart->boundary() + "' has been created twice");
+ boundaries.insert(multiPart->boundary());
+
+ RUN_REQUEST(runMultipartRequest(request, reply, multiPart, "POST"));
+ multiPart->deleteLater();
+
+ QCOMPARE(reply->url(), url);
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+
+ QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok
+
+ QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string
+ QVERIFY(multiPart->boundary().count() < 70);
+ QByteArray replyData = reply->readAll();
+
+ expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n");
+// QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above
+ QCOMPARE(replyData, expectedReplyData);
+}
+
+void tst_QNetworkReply::putToHttpMultipart_data()
+{
+ postToHttpMultipart_data();
+}
+
+void tst_QNetworkReply::putToHttpMultipart()
+{
+ QSKIP("test server script cannot handle PUT data yet", SkipAll);
+ QFETCH(QUrl, url);
+
+ static QSet<QByteArray> boundaries;
+
+ QNetworkRequest request(url);
+ QNetworkReplyPtr reply;
+
+ QFETCH(QHttpMultiPart *, multiPart);
+ QFETCH(QByteArray, expectedReplyData);
+ QFETCH(QByteArray, contentType);
+
+ // hack for testing the setting of the content-type header by hand:
+ if (contentType == "custom") {
+ QByteArray contentType("multipart/custom; boundary=\"" + multiPart->boundary() + "\"");
+ request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);
+ }
+
+ QVERIFY2(! boundaries.contains(multiPart->boundary()), "boundary '" + multiPart->boundary() + "' has been created twice");
+ boundaries.insert(multiPart->boundary());
+
+ RUN_REQUEST(runMultipartRequest(request, reply, multiPart, "PUT"));
+ multiPart->deleteLater();
+
+ QCOMPARE(reply->url(), url);
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+
+ QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); // 200 Ok
+
+ QVERIFY(multiPart->boundary().count() > 20); // check that there is randomness after the "boundary_.oOo._" string
+ QVERIFY(multiPart->boundary().count() < 70);
+ QByteArray replyData = reply->readAll();
+
+ expectedReplyData.prepend("content type: multipart/" + contentType + "; boundary=\"" + multiPart->boundary() + "\"\n");
+// QEXPECT_FAIL("nested", "the server does not understand nested multipart messages", Continue); // see above
+ QCOMPARE(replyData, expectedReplyData);
+}
+
void tst_QNetworkReply::deleteFromHttp_data()
{
QTest::addColumn<QUrl>("url");