diff options
author | Peter Hartmann <peter.hartmann@nokia.com> | 2009-12-04 16:07:49 (GMT) |
---|---|---|
committer | Peter Hartmann <peter.hartmann@nokia.com> | 2011-03-22 14:49:53 (GMT) |
commit | 8ad504b31a53ea2a7993f5217c68d0c4aa203b2d (patch) | |
tree | db4e742032317d4328c87591e8fadca051d96b45 /src/network | |
parent | e3df83e8735124dbd15107b0c083d75d99b98ea9 (diff) | |
download | Qt-8ad504b31a53ea2a7993f5217c68d0c4aa203b2d.zip Qt-8ad504b31a53ea2a7993f5217c68d0c4aa203b2d.tar.gz Qt-8ad504b31a53ea2a7993f5217c68d0c4aa203b2d.tar.bz2 |
network API: add support for HTTP multipart messages
This commit adds two new classes, QHttpPart and QHttpMultiPart, and
two new overloads to QNetworkAccessManager:
post(const QNetworkRequest &request, QHttpMultiPart *multiPart) and
put(const QNetworkRequest &request, QHttpMultiPart *multiPart).
With those classes, it is possible to do a HTTP POST with a multipart
message in a memory-saving way: The data from the parts is not copied
when read from a file or another QIODevice.
Reviewed-by: Markus Goetz
Task-number: QTBUG-6222
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/access/access.pri | 7 | ||||
-rw-r--r-- | src/network/access/qhttpmultipart.cpp | 548 | ||||
-rw-r--r-- | src/network/access/qhttpmultipart.h | 119 | ||||
-rw-r--r-- | src/network/access/qhttpmultipart_p.h | 182 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 93 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.h | 3 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager_p.h | 2 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 9 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 3 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest_p.h | 3 |
10 files changed, 964 insertions, 5 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 57a79b3..5ead3ad 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 0000000..d329d5c --- /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 0000000..0a3342c --- /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 0000000..7dc13e9 --- /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 7e75045..0337607 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 d67b8ac..47760b2 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 ee6ad70..0f18221 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 a48a26f..665ee28 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 b5ef109..d3bbba7 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 23705f5..ea8c56f 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); |