diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/network/access | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/network/access')
43 files changed, 19651 insertions, 0 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri new file mode 100644 index 0000000..6bcca0c --- /dev/null +++ b/src/network/access/access.pri @@ -0,0 +1,53 @@ +# Qt network access module + +HEADERS += access/qftp.h \ + access/qhttp.h \ + access/qhttpnetworkconnection_p.h \ + access/qnetworkaccessmanager.h \ + access/qnetworkaccessmanager_p.h \ + access/qnetworkaccesscache_p.h \ + access/qnetworkaccessbackend_p.h \ + access/qnetworkaccessdatabackend_p.h \ + access/qnetworkaccessdebugpipebackend_p.h \ + access/qnetworkaccesshttpbackend_p.h \ + access/qnetworkaccessfilebackend_p.h \ + access/qnetworkaccesscachebackend_p.h \ + access/qnetworkaccessftpbackend_p.h \ + access/qnetworkcookie.h \ + access/qnetworkcookie_p.h \ + access/qnetworkrequest.h \ + access/qnetworkrequest_p.h \ + access/qnetworkreply.h \ + access/qnetworkreply_p.h \ + access/qnetworkreplyimpl_p.h \ + access/qabstractnetworkcache_p.h \ + access/qabstractnetworkcache.h \ + access/qnetworkdiskcache_p.h \ + access/qnetworkdiskcache.h + +SOURCES += access/qftp.cpp \ + access/qhttp.cpp \ + access/qhttpnetworkconnection.cpp \ + access/qnetworkaccessmanager.cpp \ + access/qnetworkaccesscache.cpp \ + access/qnetworkaccessbackend.cpp \ + access/qnetworkaccessdatabackend.cpp \ + access/qnetworkaccessdebugpipebackend.cpp \ + access/qnetworkaccessfilebackend.cpp \ + access/qnetworkaccesscachebackend.cpp \ + access/qnetworkaccessftpbackend.cpp \ + access/qnetworkaccesshttpbackend.cpp \ + access/qnetworkcookie.cpp \ + access/qnetworkrequest.cpp \ + access/qnetworkreply.cpp \ + access/qnetworkreplyimpl.cpp \ + access/qabstractnetworkcache.cpp \ + access/qnetworkdiskcache.cpp + +#zlib support +contains(QT_CONFIG, zlib) { + INCLUDEPATH += ../3rdparty/zlib +} else:!contains(QT_CONFIG, no-zlib) { + unix:LIBS += -lz +# win32:LIBS += libz.lib +} diff --git a/src/network/access/qabstractnetworkcache.cpp b/src/network/access/qabstractnetworkcache.cpp new file mode 100644 index 0000000..3a2dd4a --- /dev/null +++ b/src/network/access/qabstractnetworkcache.cpp @@ -0,0 +1,532 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractnetworkcache.h" +#include "qabstractnetworkcache_p.h" + +#include <qdatetime.h> +#include <qurl.h> + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +class QNetworkCacheMetaDataPrivate : public QSharedData +{ + +public: + QNetworkCacheMetaDataPrivate() + : QSharedData() + , saveToDisk(true) + {} + + bool operator==(const QNetworkCacheMetaDataPrivate &other) const + { + return + url == other.url + && lastModified == other.lastModified + && expirationDate == other.expirationDate + && headers == other.headers + && saveToDisk == other.saveToDisk; + } + + QUrl url; + QDateTime lastModified; + QDateTime expirationDate; + QNetworkCacheMetaData::RawHeaderList headers; + QNetworkCacheMetaData::AttributesMap attributes; + bool saveToDisk; + + static void save(QDataStream &out, const QNetworkCacheMetaData &metaData); + static void load(QDataStream &in, QNetworkCacheMetaData &metaData); +}; +Q_GLOBAL_STATIC(QNetworkCacheMetaDataPrivate, metadata_shared_invalid) + +/*! + \class QNetworkCacheMetaData + \since 4.5 + \inmodule QtNetwork + + \brief The QNetworkCacheMetaData class provides cache information. + + QNetworkCacheMetaData provides information about a cache file including + the url, when it was last modified, when the cache file was created, headers + for file and if the file should be saved onto a disk. + + \sa QAbstractNetworkCache +*/ + +/*! + \typedef QNetworkCacheMetaData::RawHeader + + Synonym for QPair<QByteArray, QByteArray> +*/ + +/*! + \typedef QNetworkCacheMetaData::RawHeaderList + + Synonym for QList<RawHeader> +*/ + +/*! + \typedef QNetworkCacheMetaData::AttributesMap + + Synonym for QHash<QNetworkRequest::Attribute, QVariant> +*/ + +/*! + Constructs an invalid network cache meta data. + + \sa isValid() + */ +QNetworkCacheMetaData::QNetworkCacheMetaData() + : d(new QNetworkCacheMetaDataPrivate) +{ +} + +/*! + Destroys the network cache meta data. + */ +QNetworkCacheMetaData::~QNetworkCacheMetaData() +{ + // QSharedDataPointer takes care of freeing d +} + +/*! + Constructs a copy of the \a other QNetworkCacheMetaData. + */ +QNetworkCacheMetaData::QNetworkCacheMetaData(const QNetworkCacheMetaData &other) + : d(other.d) +{ +} + +/*! + Makes a copy of the \a other QNetworkCacheMetaData and returns a reference to the copy. + */ +QNetworkCacheMetaData &QNetworkCacheMetaData::operator=(const QNetworkCacheMetaData &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this meta data is equal to the \a other meta data; otherwise returns false. + + \sa operator!=() + */ +bool QNetworkCacheMetaData::operator==(const QNetworkCacheMetaData &other) const +{ + if (d == other.d) + return true; + if (d && other.d) + return *d == *other.d; + return false; +} + +/*! + \fn bool QNetworkCacheMetaData::operator!=(const QNetworkCacheMetaData &other) const + + Returns true if this meta data is not equal to the \a other meta data; otherwise returns false. + + \sa operator==() + */ + +/*! + Returns true if this network cache meta data has attributes that have been set otherwise false. + */ +bool QNetworkCacheMetaData::isValid() const +{ + return !(*d == *metadata_shared_invalid()); +} + +/*! + Returns is this cache should be allowed to be stored on disk. + + Some cache implementations can keep these cache items in memory for performance reasons, + but for security reasons they should not be written to disk. + + Specifically with http, documents marked with Pragma: no-cache, or have a Cache-control set to + no-store or no-cache or any https document that doesn't have "Cache-control: public" set will + set the saveToDisk to false. + + \sa setSaveToDisk() + */ +bool QNetworkCacheMetaData::saveToDisk() const +{ + return d->saveToDisk; +} + +/*! + Sets whether this network cache meta data and associated content should be + allowed to be stored on disk to \a allow. + + \sa saveToDisk() + */ +void QNetworkCacheMetaData::setSaveToDisk(bool allow) +{ + d->saveToDisk = allow; +} + +/*! + Returns the URL this network cache meta data is referring to. + + \sa setUrl() + */ +QUrl QNetworkCacheMetaData::url() const +{ + return d->url; +} + +/*! + Sets the URL this network cache meta data to to be \a url. + + The password and fragment are removed from the url. + + \sa url() + */ +void QNetworkCacheMetaData::setUrl(const QUrl &url) +{ + d->url = url; + d->url.setPassword(QString()); + d->url.setFragment(QString()); +} + +/*! + Returns a list of all raw headers that are set in this meta data. + The list is in the same order that the headers were set. + + \sa setRawHeaders() + */ +QNetworkCacheMetaData::RawHeaderList QNetworkCacheMetaData::rawHeaders() const +{ + return d->headers; +} + +/*! + Sets the raw headers to \a list. + + \sa rawHeaders() + */ +void QNetworkCacheMetaData::setRawHeaders(const RawHeaderList &list) +{ + d->headers = list; +} + +/*! + Returns the date and time when the meta data was last modified. + */ +QDateTime QNetworkCacheMetaData::lastModified() const +{ + return d->lastModified; +} + +/*! + Sets the date and time when the meta data was last modified to \a dateTime. + */ +void QNetworkCacheMetaData::setLastModified(const QDateTime &dateTime) +{ + d->lastModified = dateTime; +} + +/*! + Returns the date and time when the meta data expires. + */ +QDateTime QNetworkCacheMetaData::expirationDate() const +{ + return d->expirationDate; +} + +/*! + Sets the date and time when the meta data expires to \a dateTime. + */ +void QNetworkCacheMetaData::setExpirationDate(const QDateTime &dateTime) +{ + d->expirationDate = dateTime; +} + +/*! + Returns all the attributes stored with this cache item. + + \sa setAttributes(), QNetworkRequest::Attribute +*/ +QNetworkCacheMetaData::AttributesMap QNetworkCacheMetaData::attributes() const +{ + return d->attributes; +} + +/*! + Sets all attributes of this cache item to be the map \a attributes. + + \sa attributes(), QNetworkRequest::setAttribute() +*/ +void QNetworkCacheMetaData::setAttributes(const AttributesMap &attributes) +{ + d->attributes = attributes; +} + +/*! + \relates QNetworkCacheMetaData + \since 4.5 + + Writes \a metaData to the \a out stream. + + \sa {Format of the QDataStream operators} +*/ +QDataStream &operator<<(QDataStream &out, const QNetworkCacheMetaData &metaData) +{ + QNetworkCacheMetaDataPrivate::save(out, metaData); + return out; +} + +static inline QDataStream &operator<<(QDataStream &out, const QNetworkCacheMetaData::AttributesMap &hash) +{ + out << quint32(hash.size()); + QNetworkCacheMetaData::AttributesMap::ConstIterator it = hash.end(); + QNetworkCacheMetaData::AttributesMap::ConstIterator begin = hash.begin(); + while (it != begin) { + --it; + out << int(it.key()) << it.value(); + } + return out; +} + +void QNetworkCacheMetaDataPrivate::save(QDataStream &out, const QNetworkCacheMetaData &metaData) +{ + // note: if you change the contents of the meta data here + // remember to bump the cache version in qnetworkdiskcache.cpp CurrentCacheVersion + out << metaData.url(); + out << metaData.expirationDate(); + out << metaData.lastModified(); + out << metaData.saveToDisk(); + out << metaData.attributes(); + out << metaData.rawHeaders(); +} + +/*! + \relates QNetworkCacheMetaData + \since 4.5 + + Reads a QNetworkCacheMetaData from the stream \a in into \a metaData. + + \sa {Format of the QDataStream operators} +*/ +QDataStream &operator>>(QDataStream &in, QNetworkCacheMetaData &metaData) +{ + QNetworkCacheMetaDataPrivate::load(in, metaData); + return in; +} + +static inline QDataStream &operator>>(QDataStream &in, QNetworkCacheMetaData::AttributesMap &hash) +{ + hash.clear(); + QDataStream::Status oldStatus = in.status(); + in.resetStatus(); + hash.clear(); + + quint32 n; + in >> n; + + for (quint32 i = 0; i < n; ++i) { + if (in.status() != QDataStream::Ok) + break; + + int k; + QVariant t; + in >> k >> t; + hash.insertMulti(QNetworkRequest::Attribute(k), t); + } + + if (in.status() != QDataStream::Ok) + hash.clear(); + if (oldStatus != QDataStream::Ok) + in.setStatus(oldStatus); + return in; +} + +void QNetworkCacheMetaDataPrivate::load(QDataStream &in, QNetworkCacheMetaData &metaData) +{ + in >> metaData.d->url; + in >> metaData.d->expirationDate; + in >> metaData.d->lastModified; + in >> metaData.d->saveToDisk; + in >> metaData.d->attributes; + in >> metaData.d->headers; +} + +/*! + \class QAbstractNetworkCache + \since 4.5 + \inmodule QtNetwork + + \brief The QAbstractNetworkCache class provides the interface for cache implementations. + + QAbstractNetworkCache is the base class for every standard cache that is used be + QNetworkAccessManager. QAbstractNetworkCache is an abstract class and cannot be + instantiated. + + \sa QNetworkDiskCache +*/ + +/*! + Constructs an abstract network cache with the given \a parent. +*/ +QAbstractNetworkCache::QAbstractNetworkCache(QObject *parent) + : QObject(*new QAbstractNetworkCachePrivate, parent) +{ +} + +/*! + \internal +*/ +QAbstractNetworkCache::QAbstractNetworkCache(QAbstractNetworkCachePrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} + +/*! + Destroys the cache. + + Any operations that have not been inserted are discarded. + + \sa insert() + */ +QAbstractNetworkCache::~QAbstractNetworkCache() +{ +} + +/*! + \fn QNetworkCacheMetaData QAbstractNetworkCache::metaData(const QUrl &url) = 0 + Returns the meta data for the url \a url. + + If the url is valid and the cache contains the data for url, + a valid QNetworkCacheMetaData is returned. + + In the base class this is a pure virtual function. + + \sa updateMetaData(), data() +*/ + +/*! + \fn void QAbstractNetworkCache::updateMetaData(const QNetworkCacheMetaData &metaData) = 0 + Updates the cache meta date for the metaData's url to \a metaData + + If the cache does not contains a cache item for the url then no action is taken. + + In the base class this is a pure virtual function. + + \sa metaData(), prepare() +*/ + +/*! + \fn QIODevice *QAbstractNetworkCache::data(const QUrl &url) = 0 + Returns the data associated with \a url. + + It is up to the application that requests the data to delete + the QIODevice when done with it. + + If there is no cache for \a url, the url is invalid, or if there + is an internal cache error 0 is returned. + + In the base class this is a pure virtual function. + + \sa metaData(), prepare() +*/ + +/*! + \fn bool QAbstractNetworkCache::remove(const QUrl &url) = 0 + Removes the cache entry for \a url, returning true if success otherwise false. + + In the base class this is a pure virtual function. + + \sa clear(), prepare() +*/ + +/*! + \fn QIODevice *QAbstractNetworkCache::prepare(const QNetworkCacheMetaData &metaData) = 0 + Returns the device that should be populated with the data for + the cache item \a metaData. When all of the data has been written + insert() should be called. If metaData is invalid or the url in + the metadata is invalid 0 is returned. + + The cache owns the device and will take care of deleting it when + it is inserted or removed. + + To cancel a prepared inserted call remove() on the metadata's url. + + In the base class this is a pure virtual function. + + \sa remove(), updateMetaData(), insert() +*/ + +/*! + \fn void QAbstractNetworkCache::insert(QIODevice *device) = 0 + Inserts the data in \a device and the prepared meta data into the cache. + After this function is called the data and meta data should be retrievable + using data() and metaData(). + + To cancel a prepared inserted call remove() on the metadata's url. + + In the base class this is a pure virtual function. + + \sa prepare(), remove() +*/ + +/*! + \fn qint64 QAbstractNetworkCache::cacheSize() const = 0 + Returns the current size taken up by the cache. Depending upon + the cache implementation this might be disk or memory size. + + In the base class this is a pure virtual function. + + \sa clear() +*/ + +/*! + \fn void QAbstractNetworkCache::clear() = 0 + Removes all items from the cache. Unless there was failures + clearing the cache cacheSize() should return 0 after a call to clear. + + In the base class this is a pure virtual function. + + \sa cacheSize(), remove() +*/ + +QT_END_NAMESPACE diff --git a/src/network/access/qabstractnetworkcache.h b/src/network/access/qabstractnetworkcache.h new file mode 100644 index 0000000..a7eb2b1 --- /dev/null +++ b/src/network/access/qabstractnetworkcache.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTNETWORKCACHE_H +#define QABSTRACTNETWORKCACHE_H + +#include <QtCore/qobject.h> +#include <QtCore/qshareddata.h> +#include <QtCore/qpair.h> +#include <QtNetwork/qnetworkrequest.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QIODevice; +class QDateTime; +class QUrl; +template<class T> class QList; + +class QNetworkCacheMetaDataPrivate; +class Q_NETWORK_EXPORT QNetworkCacheMetaData +{ + +public: + typedef QPair<QByteArray, QByteArray> RawHeader; + typedef QList<RawHeader> RawHeaderList; + typedef QHash<QNetworkRequest::Attribute, QVariant> AttributesMap; + + QNetworkCacheMetaData(); + QNetworkCacheMetaData(const QNetworkCacheMetaData &other); + ~QNetworkCacheMetaData(); + + QNetworkCacheMetaData &operator=(const QNetworkCacheMetaData &other); + bool operator==(const QNetworkCacheMetaData &other) const; + inline bool operator!=(const QNetworkCacheMetaData &other) const + { return !(*this == other); } + + bool isValid() const; + + QUrl url() const; + void setUrl(const QUrl &url); + + RawHeaderList rawHeaders() const; + void setRawHeaders(const RawHeaderList &headers); + + QDateTime lastModified() const; + void setLastModified(const QDateTime &dateTime); + + QDateTime expirationDate() const; + void setExpirationDate(const QDateTime &dateTime); + + bool saveToDisk() const; + void setSaveToDisk(bool allow); + + AttributesMap attributes() const; + void setAttributes(const AttributesMap &attributes); + +private: + friend class QNetworkCacheMetaDataPrivate; + QSharedDataPointer<QNetworkCacheMetaDataPrivate> d; +}; + +Q_NETWORK_EXPORT QDataStream &operator<<(QDataStream &, const QNetworkCacheMetaData &); +Q_NETWORK_EXPORT QDataStream &operator>>(QDataStream &, QNetworkCacheMetaData &); + + +class QAbstractNetworkCachePrivate; +class Q_NETWORK_EXPORT QAbstractNetworkCache : public QObject +{ + Q_OBJECT + +public: + virtual ~QAbstractNetworkCache(); + + virtual QNetworkCacheMetaData metaData(const QUrl &url) = 0; + virtual void updateMetaData(const QNetworkCacheMetaData &metaData) = 0; + virtual QIODevice *data(const QUrl &url) = 0; + virtual bool remove(const QUrl &url) = 0; + virtual qint64 cacheSize() const = 0; + + virtual QIODevice *prepare(const QNetworkCacheMetaData &metaData) = 0; + virtual void insert(QIODevice *device) = 0; + +public Q_SLOTS: + virtual void clear() = 0; + +protected: + explicit QAbstractNetworkCache(QObject *parent = 0); + QAbstractNetworkCache(QAbstractNetworkCachePrivate &dd, QObject *parent); + +private: + Q_DECLARE_PRIVATE(QAbstractNetworkCache) + Q_DISABLE_COPY(QAbstractNetworkCache) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/access/qabstractnetworkcache_p.h b/src/network/access/qabstractnetworkcache_p.h new file mode 100644 index 0000000..9b6f7d1 --- /dev/null +++ b/src/network/access/qabstractnetworkcache_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTNETWORKCACHE_P_H +#define QABSTRACTNETWORKCACHE_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 framework. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QAbstractNetworkCachePrivate: public QObjectPrivate +{ +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qftp.cpp b/src/network/access/qftp.cpp new file mode 100644 index 0000000..569d2fd --- /dev/null +++ b/src/network/access/qftp.cpp @@ -0,0 +1,2407 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QFTPPI_DEBUG +//#define QFTPDTP_DEBUG + +#include "qftp.h" +#include "qabstractsocket.h" + +#ifndef QT_NO_FTP + +#include "qcoreapplication.h" +#include "qtcpsocket.h" +#include "qurlinfo.h" +#include "qstringlist.h" +#include "qregexp.h" +#include "qtimer.h" +#include "qfileinfo.h" +#include "qhash.h" +#include "qtcpserver.h" +#include "qlocale.h" + +QT_BEGIN_NAMESPACE + +class QFtpPI; + +/* + The QFtpDTP (DTP = Data Transfer Process) controls all client side + data transfer between the client and server. +*/ +class QFtpDTP : public QObject +{ + Q_OBJECT + +public: + enum ConnectState { + CsHostFound, + CsConnected, + CsClosed, + CsHostNotFound, + CsConnectionRefused + }; + + QFtpDTP(QFtpPI *p, QObject *parent = 0); + + void setData(QByteArray *); + void setDevice(QIODevice *); + void writeData(); + void setBytesTotal(qint64 bytes); + + bool hasError() const; + QString errorMessage() const; + void clearError(); + + void connectToHost(const QString & host, quint16 port); + int setupListener(const QHostAddress &address); + void waitForConnection(); + + QTcpSocket::SocketState state() const; + qint64 bytesAvailable() const; + qint64 read(char *data, qint64 maxlen); + QByteArray readAll(); + + void abortConnection(); + + static bool parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info); + +signals: + void listInfo(const QUrlInfo&); + void readyRead(); + void dataTransferProgress(qint64, qint64); + + void connectState(int); + +private slots: + void socketConnected(); + void socketReadyRead(); + void socketError(QAbstractSocket::SocketError); + void socketConnectionClosed(); + void socketBytesWritten(qint64); + void setupSocket(); + + void dataReadyRead(); + +private: + void clearData(); + + QTcpSocket *socket; + QTcpServer listener; + + QFtpPI *pi; + QString err; + qint64 bytesDone; + qint64 bytesTotal; + bool callWriteData; + + // If is_ba is true, ba is used; ba is never 0. + // Otherwise dev is used; dev can be 0 or not. + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; + + QByteArray bytesFromSocket; +}; + +/********************************************************************** + * + * QFtpPI - Protocol Interpreter + * + *********************************************************************/ + +class QFtpPI : public QObject +{ + Q_OBJECT + +public: + QFtpPI(QObject *parent = 0); + + void connectToHost(const QString &host, quint16 port); + + bool sendCommands(const QStringList &cmds); + bool sendCommand(const QString &cmd) + { return sendCommands(QStringList(cmd)); } + + void clearPendingCommands(); + void abort(); + + QString currentCommand() const + { return currentCmd; } + + bool rawCommand; + bool transferConnectionExtended; + + QFtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it + // makes the design simpler this way +signals: + void connectState(int); + void finished(const QString&); + void error(int, const QString&); + void rawFtpReply(int, const QString&); + +private slots: + void hostFound(); + void connected(); + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void error(QAbstractSocket::SocketError); + + void dtpConnectState(int); + +private: + // the states are modelled after the generalized state diagram of RFC 959, + // page 58 + enum State { + Begin, + Idle, + Waiting, + Success, + Failure + }; + + enum AbortState { + None, + AbortStarted, + WaitForAbortToFinish + }; + + bool processReply(); + bool startNextCmd(); + + QTcpSocket commandSocket; + QString replyText; + char replyCode[3]; + State state; + AbortState abortState; + QStringList pendingCommands; + QString currentCmd; + + bool waitForDtpToConnect; + bool waitForDtpToClose; + + QByteArray bytesFromSocket; + + friend class QFtpDTP; +}; + +/********************************************************************** + * + * QFtpCommand implemenatation + * + *********************************************************************/ +class QFtpCommand +{ +public: + QFtpCommand(QFtp::Command cmd, QStringList raw, const QByteArray &ba); + QFtpCommand(QFtp::Command cmd, QStringList raw, QIODevice *dev = 0); + ~QFtpCommand(); + + int id; + QFtp::Command command; + QStringList rawCmds; + + // If is_ba is true, ba is used; ba is never 0. + // Otherwise dev is used; dev can be 0 or not. + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; + + static QBasicAtomicInt idCounter; +}; + +QBasicAtomicInt QFtpCommand::idCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +QFtpCommand::QFtpCommand(QFtp::Command cmd, QStringList raw, const QByteArray &ba) + : command(cmd), rawCmds(raw), is_ba(true) +{ + id = idCounter.fetchAndAddRelaxed(1); + data.ba = new QByteArray(ba); +} + +QFtpCommand::QFtpCommand(QFtp::Command cmd, QStringList raw, QIODevice *dev) + : command(cmd), rawCmds(raw), is_ba(false) +{ + id = idCounter.fetchAndAddRelaxed(1); + data.dev = dev; +} + +QFtpCommand::~QFtpCommand() +{ + if (is_ba) + delete data.ba; +} + +/********************************************************************** + * + * QFtpDTP implemenatation + * + *********************************************************************/ +QFtpDTP::QFtpDTP(QFtpPI *p, QObject *parent) : + QObject(parent), + socket(0), + listener(this), + pi(p), + callWriteData(false) +{ + clearData(); + listener.setObjectName(QLatin1String("QFtpDTP active state server")); + connect(&listener, SIGNAL(newConnection()), SLOT(setupSocket())); +} + +void QFtpDTP::setData(QByteArray *ba) +{ + is_ba = true; + data.ba = ba; +} + +void QFtpDTP::setDevice(QIODevice *dev) +{ + is_ba = false; + data.dev = dev; +} + +void QFtpDTP::setBytesTotal(qint64 bytes) +{ + bytesTotal = bytes; + bytesDone = 0; + emit dataTransferProgress(bytesDone, bytesTotal); +} + +void QFtpDTP::connectToHost(const QString & host, quint16 port) +{ + bytesFromSocket.clear(); + + if (socket) + delete socket; + socket = new QTcpSocket(this); + socket->setObjectName(QLatin1String("QFtpDTP Passive state socket")); + connect(socket, SIGNAL(connected()), SLOT(socketConnected())); + connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed())); + connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); + + socket->connectToHost(host, port); +} + +int QFtpDTP::setupListener(const QHostAddress &address) +{ + if (!listener.isListening() && !listener.listen(address, 0)) + return -1; + return listener.serverPort(); +} + +void QFtpDTP::waitForConnection() +{ + // This function is only interesting in Active transfer mode; it works + // around a limitation in QFtp's design by blocking, waiting for an + // incoming connection. For the default Passive mode, it does nothing. + if (listener.isListening()) + listener.waitForNewConnection(); +} + +QTcpSocket::SocketState QFtpDTP::state() const +{ + return socket ? socket->state() : QTcpSocket::UnconnectedState; +} + +qint64 QFtpDTP::bytesAvailable() const +{ + if (!socket || socket->state() != QTcpSocket::ConnectedState) + return (qint64) bytesFromSocket.size(); + return socket->bytesAvailable(); +} + +qint64 QFtpDTP::read(char *data, qint64 maxlen) +{ + qint64 read; + if (socket && socket->state() == QTcpSocket::ConnectedState) { + read = socket->read(data, maxlen); + } else { + read = bytesFromSocket.size(); + memcpy(data, bytesFromSocket.data(), read); + bytesFromSocket.clear(); + } + + bytesDone += read; + return read; +} + +QByteArray QFtpDTP::readAll() +{ + QByteArray tmp; + if (socket && socket->state() == QTcpSocket::ConnectedState) { + tmp = socket->readAll(); + bytesDone += tmp.size(); + } else { + tmp = bytesFromSocket; + bytesFromSocket.clear(); + } + return tmp; +} + +void QFtpDTP::writeData() +{ + if (!socket) + return; + + if (is_ba) { +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::writeData: write %d bytes", data.ba->size()); +#endif + if (data.ba->size() == 0) + emit dataTransferProgress(0, bytesTotal); + else + socket->write(data.ba->data(), data.ba->size()); + + socket->close(); + + clearData(); + } else if (data.dev) { + callWriteData = false; + const qint64 blockSize = 16*1024; + char buf[16*1024]; + qint64 read = data.dev->read(buf, blockSize); +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::writeData: write() of size %lli bytes", read); +#endif + if (read > 0) { + socket->write(buf, read); + } else if (read == -1 || (!data.dev->isSequential() && data.dev->atEnd())) { + // error or EOF + if (bytesDone == 0 && socket->bytesToWrite() == 0) + emit dataTransferProgress(0, bytesTotal); + socket->close(); + clearData(); + } + + // do we continue uploading? + callWriteData = data.dev != 0; + } +} + +void QFtpDTP::dataReadyRead() +{ + writeData(); +} + +inline bool QFtpDTP::hasError() const +{ + return !err.isNull(); +} + +inline QString QFtpDTP::errorMessage() const +{ + return err; +} + +inline void QFtpDTP::clearError() +{ + err.clear(); +} + +void QFtpDTP::abortConnection() +{ +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::abortConnection, bytesAvailable == %lli", + socket ? socket->bytesAvailable() : (qint64) 0); +#endif + callWriteData = false; + clearData(); + + if (socket) + socket->abort(); +} + +static void _q_fixupDateTime(QDateTime *dateTime) +{ + // Adjust for future tolerance. + const int futureTolerance = 86400; + if (dateTime->secsTo(QDateTime::currentDateTime()) < -futureTolerance) { + QDate d = dateTime->date(); + d.setYMD(d.year() - 1, d.month(), d.day()); + dateTime->setDate(d); + } +} + +static void _q_parseUnixDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) +{ + // Unix style, 7 + 1 entries + // -rw-r--r-- 1 ftp ftp 17358091 Aug 10 2004 qt-x11-free-3.3.3.tar.gz + // drwxr-xr-x 3 ftp ftp 4096 Apr 14 2000 compiled-examples + // lrwxrwxrwx 1 ftp ftp 9 Oct 29 2005 qtscape -> qtmozilla + if (tokens.size() != 8) + return; + + char first = tokens.at(1).at(0).toLatin1(); + if (first == 'd') { + info->setDir(true); + info->setFile(false); + info->setSymLink(false); + } else if (first == '-') { + info->setDir(false); + info->setFile(true); + info->setSymLink(false); + } else if (first == 'l') { + info->setDir(true); + info->setFile(false); + info->setSymLink(true); + } + + // Resolve filename + QString name = tokens.at(7); + if (info->isSymLink()) { + int linkPos = name.indexOf(QLatin1String(" ->")); + if (linkPos != -1) + name.resize(linkPos); + } + info->setName(name); + + // Resolve owner & group + info->setOwner(tokens.at(3)); + info->setGroup(tokens.at(4)); + + // Resolve size + info->setSize(tokens.at(5).toLongLong()); + + QStringList formats; + formats << QLatin1String("MMM dd yyyy") << QLatin1String("MMM dd hh:mm") << QLatin1String("MMM d yyyy") + << QLatin1String("MMM d hh:mm") << QLatin1String("MMM d yyyy") << QLatin1String("MMM dd yyyy"); + + QString dateString = tokens.at(6); + dateString[0] = dateString[0].toUpper(); + + // Resolve the modification date by parsing all possible formats + QDateTime dateTime; + int n = 0; +#ifndef QT_NO_DATESTRING + do { + dateTime = QLocale::c().toDateTime(dateString, formats.at(n++)); + } while (n < formats.size() && (!dateTime.isValid())); +#endif + + if (n == 2 || n == 4) { + // Guess the year. + dateTime.setDate(QDate(QDate::currentDate().year(), + dateTime.date().month(), + dateTime.date().day())); + _q_fixupDateTime(&dateTime); + } + if (dateTime.isValid()) + info->setLastModified(dateTime); + + // Resolve permissions + int permissions = 0; + QString p = tokens.at(2); + permissions |= (p[0] == QLatin1Char('r') ? QUrlInfo::ReadOwner : 0); + permissions |= (p[1] == QLatin1Char('w') ? QUrlInfo::WriteOwner : 0); + permissions |= (p[2] == QLatin1Char('x') ? QUrlInfo::ExeOwner : 0); + permissions |= (p[3] == QLatin1Char('r') ? QUrlInfo::ReadGroup : 0); + permissions |= (p[4] == QLatin1Char('w') ? QUrlInfo::WriteGroup : 0); + permissions |= (p[5] == QLatin1Char('x') ? QUrlInfo::ExeGroup : 0); + permissions |= (p[6] == QLatin1Char('r') ? QUrlInfo::ReadOther : 0); + permissions |= (p[7] == QLatin1Char('w') ? QUrlInfo::WriteOther : 0); + permissions |= (p[8] == QLatin1Char('x') ? QUrlInfo::ExeOther : 0); + info->setPermissions(permissions); + + bool isOwner = info->owner() == userName; + info->setReadable((permissions & QUrlInfo::ReadOther) || ((permissions & QUrlInfo::ReadOwner) && isOwner)); + info->setWritable((permissions & QUrlInfo::WriteOther) || ((permissions & QUrlInfo::WriteOwner) && isOwner)); +} + +static void _q_parseDosDir(const QStringList &tokens, const QString &userName, QUrlInfo *info) +{ + // DOS style, 3 + 1 entries + // 01-16-02 11:14AM <DIR> epsgroup + // 06-05-03 03:19PM 1973 readme.txt + if (tokens.size() != 4) + return; + + Q_UNUSED(userName); + + QString name = tokens.at(3); + info->setName(name); + info->setSymLink(name.toLower().endsWith(QLatin1String(".lnk"))); + + if (tokens.at(2) == QLatin1String("<DIR>")) { + info->setFile(false); + info->setDir(true); + } else { + info->setFile(true); + info->setDir(false); + info->setSize(tokens.at(2).toLongLong()); + } + + // Note: We cannot use QFileInfo; permissions are for the server-side + // machine, and QFileInfo's behavior depends on the local platform. + int permissions = QUrlInfo::ReadOwner | QUrlInfo::WriteOwner + | QUrlInfo::ReadGroup | QUrlInfo::WriteGroup + | QUrlInfo::ReadOther | QUrlInfo::WriteOther; + QString ext; + int extIndex = name.lastIndexOf(QLatin1Char('.')); + if (extIndex != -1) + ext = name.mid(extIndex + 1); + if (ext == QLatin1String("exe") || ext == QLatin1String("bat") || ext == QLatin1String("com")) + permissions |= QUrlInfo::ExeOwner | QUrlInfo::ExeGroup | QUrlInfo::ExeOther; + info->setPermissions(permissions); + + info->setReadable(true); + info->setWritable(info->isFile()); + + QDateTime dateTime; +#ifndef QT_NO_DATESTRING + dateTime = QLocale::c().toDateTime(tokens.at(1), QLatin1String("MM-dd-yy hh:mmAP")); + if (dateTime.date().year() < 1971) { + dateTime.setDate(QDate(dateTime.date().year() + 100, + dateTime.date().month(), + dateTime.date().day())); + } +#endif + + info->setLastModified(dateTime); + +} + +bool QFtpDTP::parseDir(const QByteArray &buffer, const QString &userName, QUrlInfo *info) +{ + if (buffer.isEmpty()) + return false; + + QString bufferStr = QString::fromLatin1(buffer).trimmed(); + + // Unix style FTP servers + QRegExp unixPattern(QLatin1String("^([\\-dl])([a-zA-Z\\-]{9,9})\\s+\\d+\\s+(\\S*)\\s+" + "(\\S*)\\s+(\\d+)\\s+(\\S+\\s+\\S+\\s+\\S+)\\s+(\\S.*)")); + if (unixPattern.indexIn(bufferStr) == 0) { + _q_parseUnixDir(unixPattern.capturedTexts(), userName, info); + return true; + } + + // DOS style FTP servers + QRegExp dosPattern(QLatin1String("^(\\d\\d-\\d\\d-\\d\\d\\ \\ \\d\\d:\\d\\d[AP]M)\\s+" + "(<DIR>|\\d+)\\s+(\\S.*)$")); + if (dosPattern.indexIn(bufferStr) == 0) { + _q_parseDosDir(dosPattern.capturedTexts(), userName, info); + return true; + } + + // Unsupported + return false; +} + +void QFtpDTP::socketConnected() +{ + bytesDone = 0; +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::connectState(CsConnected)"); +#endif + emit connectState(QFtpDTP::CsConnected); +} + +void QFtpDTP::socketReadyRead() +{ + if (!socket) + return; + + if (pi->currentCommand().isEmpty()) { + socket->close(); +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::connectState(CsClosed)"); +#endif + emit connectState(QFtpDTP::CsClosed); + return; + } + + if (pi->abortState == QFtpPI::AbortStarted) { + // discard data + socket->readAll(); + return; + } + + if (pi->currentCommand().startsWith(QLatin1String("LIST"))) { + while (socket->canReadLine()) { + QUrlInfo i; + QByteArray line = socket->readLine(); +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP read (list): '%s'", line.constData()); +#endif + if (parseDir(line, QLatin1String(""), &i)) { + emit listInfo(i); + } else { + // some FTP servers don't return a 550 if the file or directory + // does not exist, but rather write a text to the data socket + // -- try to catch these cases + if (line.endsWith("No such file or directory\r\n")) + err = QString::fromLatin1(line); + } + } + } else { + if (!is_ba && data.dev) { + do { + QByteArray ba; + ba.resize(socket->bytesAvailable()); + qint64 bytesRead = socket->read(ba.data(), ba.size()); + if (bytesRead < 0) { + // a read following a readyRead() signal will + // never fail. + return; + } + ba.resize(bytesRead); + bytesDone += bytesRead; +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP read: %lli bytes (total %lli bytes)", bytesRead, bytesDone); +#endif + if (data.dev) // make sure it wasn't deleted in the slot + data.dev->write(ba); + emit dataTransferProgress(bytesDone, bytesTotal); + + // Need to loop; dataTransferProgress is often connected to + // slots that update the GUI (e.g., progress bar values), and + // if events are processed, more data may have arrived. + } while (socket->bytesAvailable()); + } else { +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP readyRead: %lli bytes available (total %lli bytes read)", + bytesAvailable(), bytesDone); +#endif + emit dataTransferProgress(bytesDone+socket->bytesAvailable(), bytesTotal); + emit readyRead(); + } + } +} + +void QFtpDTP::socketError(QAbstractSocket::SocketError e) +{ + if (e == QTcpSocket::HostNotFoundError) { +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::connectState(CsHostNotFound)"); +#endif + emit connectState(QFtpDTP::CsHostNotFound); + } else if (e == QTcpSocket::ConnectionRefusedError) { +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::connectState(CsConnectionRefused)"); +#endif + emit connectState(QFtpDTP::CsConnectionRefused); + } +} + +void QFtpDTP::socketConnectionClosed() +{ + if (!is_ba && data.dev) { + clearData(); + } + + bytesFromSocket = socket->readAll(); +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::connectState(CsClosed)"); +#endif + emit connectState(QFtpDTP::CsClosed); +} + +void QFtpDTP::socketBytesWritten(qint64 bytes) +{ + bytesDone += bytes; +#if defined(QFTPDTP_DEBUG) + qDebug("QFtpDTP::bytesWritten(%lli)", bytesDone); +#endif + emit dataTransferProgress(bytesDone, bytesTotal); + if (callWriteData) + writeData(); +} + +void QFtpDTP::setupSocket() +{ + socket = listener.nextPendingConnection(); + socket->setObjectName(QLatin1String("QFtpDTP Active state socket")); + connect(socket, SIGNAL(connected()), SLOT(socketConnected())); + connect(socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed())); + connect(socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); + + listener.close(); +} + +void QFtpDTP::clearData() +{ + is_ba = false; + data.dev = 0; +} + +/********************************************************************** + * + * QFtpPI implemenatation + * + *********************************************************************/ +QFtpPI::QFtpPI(QObject *parent) : + QObject(parent), + rawCommand(false), + transferConnectionExtended(true), + dtp(this), + commandSocket(0), + state(Begin), abortState(None), + currentCmd(QString()), + waitForDtpToConnect(false), + waitForDtpToClose(false) +{ + commandSocket.setObjectName(QLatin1String("QFtpPI_socket")); + connect(&commandSocket, SIGNAL(hostFound()), + SLOT(hostFound())); + connect(&commandSocket, SIGNAL(connected()), + SLOT(connected())); + connect(&commandSocket, SIGNAL(disconnected()), + SLOT(connectionClosed())); + connect(&commandSocket, SIGNAL(readyRead()), + SLOT(readyRead())); + connect(&commandSocket, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(error(QAbstractSocket::SocketError))); + + connect(&dtp, SIGNAL(connectState(int)), + SLOT(dtpConnectState(int))); +} + +void QFtpPI::connectToHost(const QString &host, quint16 port) +{ + emit connectState(QFtp::HostLookup); + commandSocket.connectToHost(host, port); +} + +/* + Sends the sequence of commands \a cmds to the FTP server. When the commands + are all done the finished() signal is emitted. When an error occurs, the + error() signal is emitted. + + If there are pending commands in the queue this functions returns false and + the \a cmds are not added to the queue; otherwise it returns true. +*/ +bool QFtpPI::sendCommands(const QStringList &cmds) +{ + if (!pendingCommands.isEmpty()) + return false; + + if (commandSocket.state() != QTcpSocket::ConnectedState || state!=Idle) { + emit error(QFtp::NotConnected, QFtp::tr("Not connected")); + return true; // there are no pending commands + } + + pendingCommands = cmds; + startNextCmd(); + return true; +} + +void QFtpPI::clearPendingCommands() +{ + pendingCommands.clear(); + dtp.abortConnection(); + currentCmd.clear(); + state = Idle; +} + +void QFtpPI::abort() +{ + pendingCommands.clear(); + + if (abortState != None) + // ABOR already sent + return; + + abortState = AbortStarted; +#if defined(QFTPPI_DEBUG) + qDebug("QFtpPI send: ABOR"); +#endif + commandSocket.write("ABOR\r\n", 6); + + if (currentCmd.startsWith(QLatin1String("STOR "))) + dtp.abortConnection(); +} + +void QFtpPI::hostFound() +{ + emit connectState(QFtp::Connecting); +} + +void QFtpPI::connected() +{ + state = Begin; +#if defined(QFTPPI_DEBUG) +// qDebug("QFtpPI state: %d [connected()]", state); +#endif + emit connectState(QFtp::Connected); +} + +void QFtpPI::connectionClosed() +{ + commandSocket.close(); + emit connectState(QFtp::Unconnected); +} + +void QFtpPI::delayedCloseFinished() +{ + emit connectState(QFtp::Unconnected); +} + +void QFtpPI::error(QAbstractSocket::SocketError e) +{ + if (e == QTcpSocket::HostNotFoundError) { + emit connectState(QFtp::Unconnected); + emit error(QFtp::HostNotFound, + QFtp::tr("Host %1 not found").arg(commandSocket.peerName())); + } else if (e == QTcpSocket::ConnectionRefusedError) { + emit connectState(QFtp::Unconnected); + emit error(QFtp::ConnectionRefused, + QFtp::tr("Connection refused to host %1").arg(commandSocket.peerName())); + } else if (e == QTcpSocket::SocketTimeoutError) { + emit connectState(QFtp::Unconnected); + emit error(QFtp::ConnectionRefused, + QFtp::tr("Connection timed out to host %1").arg(commandSocket.peerName())); + } +} + +void QFtpPI::readyRead() +{ + if (waitForDtpToClose) + return; + + while (commandSocket.canReadLine()) { + // read line with respect to line continuation + QString line = QString::fromAscii(commandSocket.readLine()); + if (replyText.isEmpty()) { + if (line.length() < 3) { + // protocol error + return; + } + const int lowerLimit[3] = {1,0,0}; + const int upperLimit[3] = {5,5,9}; + for (int i=0; i<3; i++) { + replyCode[i] = line[i].digitValue(); + if (replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i]) { + // protocol error + return; + } + } + } + QString endOfMultiLine; + endOfMultiLine[0] = '0' + replyCode[0]; + endOfMultiLine[1] = '0' + replyCode[1]; + endOfMultiLine[2] = '0' + replyCode[2]; + endOfMultiLine[3] = QLatin1Char(' '); + QString lineCont(endOfMultiLine); + lineCont[3] = QLatin1Char('-'); + QString lineLeft4 = line.left(4); + + while (lineLeft4 != endOfMultiLine) { + if (lineLeft4 == lineCont) + replyText += line.mid(4); // strip 'xyz-' + else + replyText += line; + if (!commandSocket.canReadLine()) + return; + line = QString::fromAscii(commandSocket.readLine()); + lineLeft4 = line.left(4); + } + replyText += line.mid(4); // strip reply code 'xyz ' + if (replyText.endsWith(QLatin1String("\r\n"))) + replyText.chop(2); + + if (processReply()) + replyText = QLatin1String(""); + } +} + +/* + Process a reply from the FTP server. + + Returns true if the reply was processed or false if the reply has to be + processed at a later point. +*/ +bool QFtpPI::processReply() +{ +#if defined(QFTPPI_DEBUG) +// qDebug("QFtpPI state: %d [processReply() begin]", state); + if (replyText.length() < 400) + qDebug("QFtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.toLatin1().constData()); + else + qDebug("QFtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2]); +#endif + + int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2]; + + // process 226 replies ("Closing Data Connection") only when the data + // connection is really closed to avoid short reads of the DTP + if (replyCodeInt == 226 || (replyCodeInt == 250 && currentCmd.startsWith(QLatin1String("RETR")))) { + if (dtp.state() != QTcpSocket::UnconnectedState) { + waitForDtpToClose = true; + return false; + } + } + + switch (abortState) { + case AbortStarted: + abortState = WaitForAbortToFinish; + break; + case WaitForAbortToFinish: + abortState = None; + return true; + default: + break; + } + + // get new state + static const State table[5] = { + /* 1yz 2yz 3yz 4yz 5yz */ + Waiting, Success, Idle, Failure, Failure + }; + switch (state) { + case Begin: + if (replyCode[0] == 1) { + return true; + } else if (replyCode[0] == 2) { + state = Idle; + emit finished(QFtp::tr("Connected to host %1").arg(commandSocket.peerName())); + break; + } + // reply codes not starting with 1 or 2 are not handled. + return true; + case Waiting: + if (static_cast<signed char>(replyCode[0]) < 0 || replyCode[0] > 5) + state = Failure; + else +#if defined(Q_OS_IRIX) && defined(Q_CC_GNU) + { + // work around a crash on 64 bit gcc IRIX + State *t = (State *) table; + state = t[replyCode[0] - 1]; + } +#else + if (replyCodeInt == 202) + state = Failure; + else + state = table[replyCode[0] - 1]; +#endif + break; + default: + // ignore unrequested message + return true; + } +#if defined(QFTPPI_DEBUG) +// qDebug("QFtpPI state: %d [processReply() intermediate]", state); +#endif + + // special actions on certain replies + emit rawFtpReply(replyCodeInt, replyText); + if (rawCommand) { + rawCommand = false; + } else if (replyCodeInt == 227) { + // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) + // rfc959 does not define this response precisely, and gives + // both examples where the parenthesis are used, and where + // they are missing. We need to scan for the address and host + // info. + QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)")); + if (addrPortPattern.indexIn(replyText) == -1) { +#if defined(QFTPPI_DEBUG) + qDebug("QFtp: bad 227 response -- address and port information missing"); +#endif + // this error should be reported + } else { + QStringList lst = addrPortPattern.capturedTexts(); + QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4]; + quint16 port = (lst[5].toUInt() << 8) + lst[6].toUInt(); + waitForDtpToConnect = true; + dtp.connectToHost(host, port); + } + } else if (replyCodeInt == 229) { + // 229 Extended Passive mode OK (|||10982|) + int portPos = replyText.indexOf(QLatin1Char('(')); + if (portPos == -1) { +#if defined(QFTPPI_DEBUG) + qDebug("QFtp: bad 229 response -- port information missing"); +#endif + // this error should be reported + } else { + ++portPos; + QChar delimiter = replyText.at(portPos); + QStringList epsvParameters = replyText.mid(portPos).split(delimiter); + + waitForDtpToConnect = true; + dtp.connectToHost(commandSocket.peerAddress().toString(), + epsvParameters.at(3).toInt()); + } + + } else if (replyCodeInt == 230) { + if (currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 && + pendingCommands.first().startsWith(QLatin1String("PASS "))) { + // no need to send the PASS -- we are already logged in + pendingCommands.pop_front(); + } + // 230 User logged in, proceed. + emit connectState(QFtp::LoggedIn); + } else if (replyCodeInt == 213) { + // 213 File status. + if (currentCmd.startsWith(QLatin1String("SIZE "))) + dtp.setBytesTotal(replyText.simplified().toLongLong()); + } else if (replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR "))) { + dtp.waitForConnection(); + dtp.writeData(); + } + + // react on new state + switch (state) { + case Begin: + // should never happen + break; + case Success: + // success handling + state = Idle; + // no break! + case Idle: + if (dtp.hasError()) { + emit error(QFtp::UnknownError, dtp.errorMessage()); + dtp.clearError(); + } + startNextCmd(); + break; + case Waiting: + // do nothing + break; + case Failure: + // If the EPSV or EPRT commands fail, replace them with + // the old PASV and PORT instead and try again. + if (currentCmd.startsWith(QLatin1String("EPSV"))) { + transferConnectionExtended = false; + pendingCommands.prepend(QLatin1String("PASV\r\n")); + } else if (currentCmd.startsWith(QLatin1String("EPRT"))) { + transferConnectionExtended = false; + pendingCommands.prepend(QLatin1String("PORT\r\n")); + } else { + emit error(QFtp::UnknownError, replyText); + } + if (state != Waiting) { + state = Idle; + startNextCmd(); + } + break; + } +#if defined(QFTPPI_DEBUG) +// qDebug("QFtpPI state: %d [processReply() end]", state); +#endif + return true; +} + +/* + Starts next pending command. Returns false if there are no pending commands, + otherwise it returns true. +*/ +bool QFtpPI::startNextCmd() +{ + if (waitForDtpToConnect) + // don't process any new commands until we are connected + return true; + +#if defined(QFTPPI_DEBUG) + if (state != Idle) + qDebug("QFtpPI startNextCmd: Internal error! QFtpPI called in non-Idle state %d", state); +#endif + if (pendingCommands.isEmpty()) { + currentCmd.clear(); + emit finished(replyText); + return false; + } + currentCmd = pendingCommands.first(); + + // PORT and PASV are edited in-place, depending on whether we + // should try the extended transfer connection commands EPRT and + // EPSV. The PORT command also triggers setting up a listener, and + // the address/port arguments are edited in. + QHostAddress address = commandSocket.localAddress(); + if (currentCmd.startsWith(QLatin1String("PORT"))) { + if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) { + int port = dtp.setupListener(address); + currentCmd = QLatin1String("EPRT |"); + currentCmd += (address.protocol() == QTcpSocket::IPv4Protocol) ? QLatin1Char('1') : QLatin1Char('2'); + currentCmd += QLatin1Char('|') + address.toString() + QLatin1Char('|') + QString::number(port); + currentCmd += QLatin1Char('|'); + } else if (address.protocol() == QTcpSocket::IPv4Protocol) { + int port = dtp.setupListener(address); + QString portArg; + quint32 ip = address.toIPv4Address(); + portArg += QString::number((ip & 0xff000000) >> 24); + portArg += QLatin1Char(',') + QString::number((ip & 0xff0000) >> 16); + portArg += QLatin1Char(',') + QString::number((ip & 0xff00) >> 8); + portArg += QLatin1Char(',') + QString::number(ip & 0xff); + portArg += QLatin1Char(',') + QString::number((port & 0xff00) >> 8); + portArg += QLatin1Char(',') + QString::number(port & 0xff); + + currentCmd = QLatin1String("PORT "); + currentCmd += portArg; + } else { + // No IPv6 connection can be set up with the PORT + // command. + return false; + } + + currentCmd += QLatin1String("\r\n"); + } else if (currentCmd.startsWith(QLatin1String("PASV"))) { + if ((address.protocol() == QTcpSocket::IPv6Protocol) && transferConnectionExtended) + currentCmd = QLatin1String("EPSV\r\n"); + } + + pendingCommands.pop_front(); +#if defined(QFTPPI_DEBUG) + qDebug("QFtpPI send: %s", currentCmd.left(currentCmd.length()-2).toLatin1().constData()); +#endif + state = Waiting; + commandSocket.write(currentCmd.toLatin1()); + return true; +} + +void QFtpPI::dtpConnectState(int s) +{ + switch (s) { + case QFtpDTP::CsClosed: + if (waitForDtpToClose) { + // there is an unprocessed reply + if (processReply()) + replyText = QLatin1String(""); + else + return; + } + waitForDtpToClose = false; + readyRead(); + return; + case QFtpDTP::CsConnected: + waitForDtpToConnect = false; + startNextCmd(); + return; + case QFtpDTP::CsHostNotFound: + case QFtpDTP::CsConnectionRefused: + emit error(QFtp::ConnectionRefused, + QFtp::tr("Connection refused for data connection")); + startNextCmd(); + return; + default: + return; + } +} + +/********************************************************************** + * + * QFtpPrivate + * + *********************************************************************/ + +QT_BEGIN_INCLUDE_NAMESPACE +#include <private/qobject_p.h> +QT_END_INCLUDE_NAMESPACE + +class QFtpPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QFtp) +public: + + inline QFtpPrivate() : close_waitForStateChange(false), state(QFtp::Unconnected), + transferMode(QFtp::Passive), error(QFtp::NoError) + { } + + ~QFtpPrivate() { while (!pending.isEmpty()) delete pending.takeFirst(); } + + // private slots + void _q_startNextCommand(); + void _q_piFinished(const QString&); + void _q_piError(int, const QString&); + void _q_piConnectState(int); + void _q_piFtpReply(int, const QString&); + + int addCommand(QFtpCommand *cmd); + + QFtpPI pi; + QList<QFtpCommand *> pending; + bool close_waitForStateChange; + QFtp::State state; + QFtp::TransferMode transferMode; + QFtp::Error error; + QString errorString; + + QString host; + quint16 port; + QString proxyHost; + quint16 proxyPort; +}; + +int QFtpPrivate::addCommand(QFtpCommand *cmd) +{ + pending.append(cmd); + + if (pending.count() == 1) { + // don't emit the commandStarted() signal before the ID is returned + QTimer::singleShot(0, q_func(), SLOT(_q_startNextCommand())); + } + return cmd->id; +} + +/********************************************************************** + * + * QFtp implementation + * + *********************************************************************/ +/*! + \class QFtp + \brief The QFtp class provides an implementation of the client side of FTP protocol. + + \ingroup io + \inmodule QtNetwork + \mainclass + + This class provides a direct interface to FTP that allows you to + have more control over the requests. However, for new + applications, it is recommended to use QNetworkAccessManager and + QNetworkReply, as those classes possess a simpler, yet more + powerful API. + + The class works asynchronously, so there are no blocking + functions. If an operation cannot be executed immediately, the + function will still return straight away and the operation will be + scheduled for later execution. The results of scheduled operations + are reported via signals. This approach depends on the event loop + being in operation. + + The operations that can be scheduled (they are called "commands" + in the rest of the documentation) are the following: + connectToHost(), login(), close(), list(), cd(), get(), put(), + remove(), mkdir(), rmdir(), rename() and rawCommand(). + + All of these commands return a unique identifier that allows you + to keep track of the command that is currently being executed. + When the execution of a command starts, the commandStarted() + signal with the command's identifier is emitted. When the command + is finished, the commandFinished() signal is emitted with the + command's identifier and a bool that indicates whether the command + finished with an error. + + In some cases, you might want to execute a sequence of commands, + e.g. if you want to connect and login to a FTP server. This is + simply achieved: + + \snippet doc/src/snippets/code/src_network_access_qftp.cpp 0 + + In this case two FTP commands have been scheduled. When the last + scheduled command has finished, a done() signal is emitted with + a bool argument that tells you whether the sequence finished with + an error. + + If an error occurs during the execution of one of the commands in + a sequence of commands, all the pending commands (i.e. scheduled, + but not yet executed commands) are cleared and no signals are + emitted for them. + + Some commands, e.g. list(), emit additional signals to report + their results. + + Example: If you want to download the INSTALL file from Trolltech's + FTP server, you would write this: + + \snippet doc/src/snippets/code/src_network_access_qftp.cpp 1 + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \snippet doc/src/snippets/code/src_network_access_qftp.cpp 2 + + The dataTransferProgress() signal in the above example is useful + if you want to show a \link QProgressBar progress bar \endlink to + inform the user about the progress of the download. The + readyRead() signal tells you that there is data ready to be read. + The amount of data can be queried then with the bytesAvailable() + function and it can be read with the read() or readAll() + function. + + If the login fails for the above example, the signals would look + like this: + + \snippet doc/src/snippets/code/src_network_access_qftp.cpp 3 + + You can then get details about the error with the error() and + errorString() functions. + + For file transfer, QFtp can use both active or passive mode, and + it uses passive file transfer mode by default; see the + documentation for setTransferMode() for more details about this. + + Call setProxy() to make QFtp connect via an FTP proxy server. + + The functions currentId() and currentCommand() provide more + information about the currently executing command. + + The functions hasPendingCommands() and clearPendingCommands() + allow you to query and clear the list of pending commands. + + If you are an experienced network programmer and want to have + complete control you can use rawCommand() to execute arbitrary FTP + commands. + + \warning The current version of QFtp doesn't fully support + non-Unix FTP servers. + + \sa QHttp, QNetworkAccessManager, QNetworkRequest, QNetworkReply, + {FTP Example} +*/ + + +/*! + Constructs a QFtp object with the given \a parent. +*/ +QFtp::QFtp(QObject *parent) + : QObject(*new QFtpPrivate, parent) +{ + Q_D(QFtp); + d->errorString = tr("Unknown error"); + + connect(&d->pi, SIGNAL(connectState(int)), + SLOT(_q_piConnectState(int))); + connect(&d->pi, SIGNAL(finished(QString)), + SLOT(_q_piFinished(QString))); + connect(&d->pi, SIGNAL(error(int,QString)), + SLOT(_q_piError(int,QString))); + connect(&d->pi, SIGNAL(rawFtpReply(int,QString)), + SLOT(_q_piFtpReply(int,QString))); + + connect(&d->pi.dtp, SIGNAL(readyRead()), + SIGNAL(readyRead())); + connect(&d->pi.dtp, SIGNAL(dataTransferProgress(qint64,qint64)), + SIGNAL(dataTransferProgress(qint64,qint64))); + connect(&d->pi.dtp, SIGNAL(listInfo(QUrlInfo)), + SIGNAL(listInfo(QUrlInfo))); +} + +#ifdef QT3_SUPPORT +/*! + Use one of the constructors that doesn't take the \a name + argument and then use setObjectName() instead. +*/ +QFtp::QFtp(QObject *parent, const char *name) + : QObject(*new QFtpPrivate, parent) +{ + Q_D(QFtp); + setObjectName(QLatin1String(name)); + d->errorString = tr("Unknown error"); + + connect(&d->pi, SIGNAL(connectState(int)), + SLOT(_q_piConnectState(int))); + connect(&d->pi, SIGNAL(finished(QString)), + SLOT(_q_piFinished(QString))); + connect(&d->pi, SIGNAL(error(int,QString)), + SLOT(_q_piError(int,QString))); + connect(&d->pi, SIGNAL(rawFtpReply(int,QString)), + SLOT(_q_piFtpReply(int,QString))); + + connect(&d->pi.dtp, SIGNAL(readyRead()), + SIGNAL(readyRead())); + connect(&d->pi.dtp, SIGNAL(dataTransferProgress(qint64,qint64)), + SIGNAL(dataTransferProgress(qint64,qint64))); + connect(&d->pi.dtp, SIGNAL(listInfo(QUrlInfo)), + SIGNAL(listInfo(QUrlInfo))); +} +#endif + +/*! + \enum QFtp::State + + This enum defines the connection state: + + \value Unconnected There is no connection to the host. + \value HostLookup A host name lookup is in progress. + \value Connecting An attempt to connect to the host is in progress. + \value Connected Connection to the host has been achieved. + \value LoggedIn Connection and user login have been achieved. + \value Closing The connection is closing down, but it is not yet + closed. (The state will be \c Unconnected when the connection is + closed.) + + \sa stateChanged() state() +*/ +/*! + \enum QFtp::TransferMode + + FTP works with two socket connections; one for commands and + another for transmitting data. While the command connection is + always initiated by the client, the second connection can be + initiated by either the client or the server. + + This enum defines whether the client (Passive mode) or the server + (Active mode) should set up the data connection. + + \value Passive The client connects to the server to transmit its + data. + + \value Active The server connects to the client to transmit its + data. +*/ +/*! + \enum QFtp::TransferType + + This enum identifies the data transfer type used with get and + put commands. + + \value Binary The data will be transferred in Binary mode. + + \value Ascii The data will be transferred in Ascii mode and new line + characters will be converted to the local format. +*/ +/*! + \enum QFtp::Error + + This enum identifies the error that occurred. + + \value NoError No error occurred. + \value HostNotFound The host name lookup failed. + \value ConnectionRefused The server refused the connection. + \value NotConnected Tried to send a command, but there is no connection to + a server. + \value UnknownError An error other than those specified above + occurred. + + \sa error() +*/ + +/*! + \enum QFtp::Command + + This enum is used as the return value for the currentCommand() function. + This allows you to perform specific actions for particular + commands, e.g. in a FTP client, you might want to clear the + directory view when a list() command is started; in this case you + can simply check in the slot connected to the start() signal if + the currentCommand() is \c List. + + \value None No command is being executed. + \value SetTransferMode set the \link TransferMode transfer\endlink mode. + \value SetProxy switch proxying on or off. + \value ConnectToHost connectToHost() is being executed. + \value Login login() is being executed. + \value Close close() is being executed. + \value List list() is being executed. + \value Cd cd() is being executed. + \value Get get() is being executed. + \value Put put() is being executed. + \value Remove remove() is being executed. + \value Mkdir mkdir() is being executed. + \value Rmdir rmdir() is being executed. + \value Rename rename() is being executed. + \value RawCommand rawCommand() is being executed. + + \sa currentCommand() +*/ + +/*! + \fn void QFtp::stateChanged(int state) + + This signal is emitted when the state of the connection changes. + The argument \a state is the new state of the connection; it is + one of the \l State values. + + It is usually emitted in response to a connectToHost() or close() + command, but it can also be emitted "spontaneously", e.g. when the + server closes the connection unexpectedly. + + \sa connectToHost() close() state() State +*/ + +/*! + \fn void QFtp::listInfo(const QUrlInfo &i); + + This signal is emitted for each directory entry the list() command + finds. The details of the entry are stored in \a i. + + \sa list() +*/ + +/*! + \fn void QFtp::commandStarted(int id) + + This signal is emitted when processing the command identified by + \a id starts. + + \sa commandFinished() done() +*/ + +/*! + \fn void QFtp::commandFinished(int id, bool error) + + This signal is emitted when processing the command identified by + \a id has finished. \a error is true if an error occurred during + the processing; otherwise \a error is false. + + \sa commandStarted() done() error() errorString() +*/ + +/*! + \fn void QFtp::done(bool error) + + This signal is emitted when the last pending command has finished; + (it is emitted after the last command's commandFinished() signal). + \a error is true if an error occurred during the processing; + otherwise \a error is false. + + \sa commandFinished() error() errorString() +*/ + +/*! + \fn void QFtp::readyRead() + + This signal is emitted in response to a get() command when there + is new data to read. + + If you specify a device as the second argument in the get() + command, this signal is \e not emitted; instead the data is + written directly to the device. + + You can read the data with the readAll() or read() functions. + + This signal is useful if you want to process the data in chunks as + soon as it becomes available. If you are only interested in the + complete data, just connect to the commandFinished() signal and + read the data then instead. + + \sa get() read() readAll() bytesAvailable() +*/ + +/*! + \fn void QFtp::dataTransferProgress(qint64 done, qint64 total) + + This signal is emitted in response to a get() or put() request to + indicate the current progress of the download or upload. + + \a done is the amount of data that has already been transferred + and \a total is the total amount of data to be read or written. It + is possible that the QFtp class is not able to determine the total + amount of data that should be transferred, in which case \a total + is 0. (If you connect this signal to a QProgressBar, the progress + bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa get(), put(), QProgressBar +*/ + +/*! + \fn void QFtp::rawCommandReply(int replyCode, const QString &detail); + + This signal is emitted in response to the rawCommand() function. + \a replyCode is the 3 digit reply code and \a detail is the text + that follows the reply code. + + \sa rawCommand() +*/ + +/*! + Connects to the FTP server \a host using port \a port. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c HostLookup, then \c + Connecting, then \c Connected. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa stateChanged() commandStarted() commandFinished() +*/ +int QFtp::connectToHost(const QString &host, quint16 port) +{ + d_func()->pi.transferConnectionExtended = true; + QStringList cmds; + cmds << host; + cmds << QString::number((uint)port); + return d_func()->addCommand(new QFtpCommand(ConnectToHost, cmds)); +} + +/*! + Logs in to the FTP server with the username \a user and the + password \a password. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c LoggedIn. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::login(const QString &user, const QString &password) +{ + QStringList cmds; + cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n")); + cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n")); + return d_func()->addCommand(new QFtpCommand(Login, cmds)); +} + +/*! + Closes the connection to the FTP server. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c Closing, then \c + Unconnected. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa stateChanged() commandStarted() commandFinished() +*/ +int QFtp::close() +{ + return d_func()->addCommand(new QFtpCommand(Close, QStringList(QLatin1String("QUIT\r\n")))); +} + +/*! + Sets the current FTP transfer mode to \a mode. The default is QFtp::Passive. + + \sa QFtp::TransferMode +*/ +int QFtp::setTransferMode(TransferMode mode) +{ + d_func()->pi.transferConnectionExtended = true; + d_func()->transferMode = mode; + return d_func()->addCommand(new QFtpCommand(SetTransferMode, QStringList())); +} + +/*! + Enables use of the FTP proxy on host \a host and port \a + port. Calling this function with \a host empty disables proxying. + + QFtp does not support FTP-over-HTTP proxy servers. Use QHttp for + this. +*/ +int QFtp::setProxy(const QString &host, quint16 port) +{ + QStringList args; + args << host << QString::number(port); + return d_func()->addCommand(new QFtpCommand(SetProxy, args)); +} + +/*! + Lists the contents of directory \a dir on the FTP server. If \a + dir is empty, it lists the contents of the current directory. + + The listInfo() signal is emitted for each directory entry found. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa listInfo() commandStarted() commandFinished() +*/ +int QFtp::list(const QString &dir) +{ + QStringList cmds; + cmds << QLatin1String("TYPE A\r\n"); + cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); + if (dir.isEmpty()) + cmds << QLatin1String("LIST\r\n"); + else + cmds << (QLatin1String("LIST ") + dir + QLatin1String("\r\n")); + return d_func()->addCommand(new QFtpCommand(List, cmds)); +} + +/*! + Changes the working directory of the server to \a dir. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::cd(const QString &dir) +{ + return d_func()->addCommand(new QFtpCommand(Cd, QStringList(QLatin1String("CWD ") + dir + QLatin1String("\r\n")))); +} + +/*! + Downloads the file \a file from the server. + + If \a dev is 0, then the readyRead() signal is emitted when there + is data available to read. You can then read the data with the + read() or readAll() functions. + + If \a dev is not 0, the data is written directly to the device \a + dev. Make sure that the \a dev pointer is valid for the duration + of the operation (it is safe to delete it when the + commandFinished() signal is emitted). In this case the readyRead() + signal is \e not emitted and you cannot read data with the + read() or readAll() functions. + + If you don't read the data immediately it becomes available, i.e. + when the readyRead() signal is emitted, it is still available + until the next command is started. + + For example, if you want to present the data to the user as soon + as there is something available, connect to the readyRead() signal + and read the data immediately. On the other hand, if you only want + to work with the complete data, you can connect to the + commandFinished() signal and read the data when the get() command + is finished. + + The data is transferred as Binary or Ascii depending on the value + of \a type. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa readyRead() dataTransferProgress() commandStarted() + commandFinished() +*/ +int QFtp::get(const QString &file, QIODevice *dev, TransferType type) +{ + QStringList cmds; + cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n"); + if (type == Binary) + cmds << QLatin1String("TYPE I\r\n"); + else + cmds << QLatin1String("TYPE A\r\n"); + cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); + cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n"); + return d_func()->addCommand(new QFtpCommand(Get, cmds, dev)); +} + +/*! + \overload + + Writes a copy of the given \a data to the file called \a file on + the server. The progress of the upload is reported by the + dataTransferProgress() signal. + + The data is transferred as Binary or Ascii depending on the value + of \a type. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + Since this function takes a copy of the \a data, you can discard + your own copy when this function returns. + + \sa dataTransferProgress() commandStarted() commandFinished() +*/ +int QFtp::put(const QByteArray &data, const QString &file, TransferType type) +{ + QStringList cmds; + if (type == Binary) + cmds << QLatin1String("TYPE I\r\n"); + else + cmds << QLatin1String("TYPE A\r\n"); + cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); + cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n"); + cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); + return d_func()->addCommand(new QFtpCommand(Put, cmds, data)); +} + +/*! + Reads the data from the IO device \a dev, and writes it to the + file called \a file on the server. The data is read in chunks from + the IO device, so this overload allows you to transmit large + amounts of data without the need to read all the data into memory + at once. + + The data is transferred as Binary or Ascii depending on the value + of \a type. + + Make sure that the \a dev pointer is valid for the duration of the + operation (it is safe to delete it when the commandFinished() is + emitted). +*/ +int QFtp::put(QIODevice *dev, const QString &file, TransferType type) +{ + QStringList cmds; + if (type == Binary) + cmds << QLatin1String("TYPE I\r\n"); + else + cmds << QLatin1String("TYPE A\r\n"); + cmds << QLatin1String(d_func()->transferMode == Passive ? "PASV\r\n" : "PORT\r\n"); + if (!dev->isSequential()) + cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n"); + cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n"); + return d_func()->addCommand(new QFtpCommand(Put, cmds, dev)); +} + +/*! + Deletes the file called \a file from the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::remove(const QString &file) +{ + return d_func()->addCommand(new QFtpCommand(Remove, QStringList(QLatin1String("DELE ") + file + QLatin1String("\r\n")))); +} + +/*! + Creates a directory called \a dir on the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::mkdir(const QString &dir) +{ + return d_func()->addCommand(new QFtpCommand(Mkdir, QStringList(QLatin1String("MKD ") + dir + QLatin1String("\r\n")))); +} + +/*! + Removes the directory called \a dir from the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::rmdir(const QString &dir) +{ + return d_func()->addCommand(new QFtpCommand(Rmdir, QStringList(QLatin1String("RMD ") + dir + QLatin1String("\r\n")))); +} + +/*! + Renames the file called \a oldname to \a newname on the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int QFtp::rename(const QString &oldname, const QString &newname) +{ + QStringList cmds; + cmds << QLatin1String("RNFR ") + oldname + QLatin1String("\r\n"); + cmds << QLatin1String("RNTO ") + newname + QLatin1String("\r\n"); + return d_func()->addCommand(new QFtpCommand(Rename, cmds)); +} + +/*! + Sends the raw FTP command \a command to the FTP server. This is + useful for low-level FTP access. If the operation you wish to + perform has an equivalent QFtp function, we recommend using the + function instead of raw FTP commands since the functions are + easier and safer. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa rawCommandReply() commandStarted() commandFinished() +*/ +int QFtp::rawCommand(const QString &command) +{ + QString cmd = command.trimmed() + QLatin1String("\r\n"); + return d_func()->addCommand(new QFtpCommand(RawCommand, QStringList(cmd))); +} + +/*! + Returns the number of bytes that can be read from the data socket + at the moment. + + \sa get() readyRead() read() readAll() +*/ +qint64 QFtp::bytesAvailable() const +{ + return d_func()->pi.dtp.bytesAvailable(); +} + +/*! \fn qint64 QFtp::readBlock(char *data, quint64 maxlen) + + Use read() instead. +*/ + +/*! + Reads \a maxlen bytes from the data socket into \a data and + returns the number of bytes read. Returns -1 if an error occurred. + + \sa get() readyRead() bytesAvailable() readAll() +*/ +qint64 QFtp::read(char *data, qint64 maxlen) +{ + return d_func()->pi.dtp.read(data, maxlen); +} + +/*! + Reads all the bytes available from the data socket and returns + them. + + \sa get() readyRead() bytesAvailable() read() +*/ +QByteArray QFtp::readAll() +{ + return d_func()->pi.dtp.readAll(); +} + +/*! + Aborts the current command and deletes all scheduled commands. + + If there is an unfinished command (i.e. a command for which the + commandStarted() signal has been emitted, but for which the + commandFinished() signal has not been emitted), this function + sends an \c ABORT command to the server. When the server replies + that the command is aborted, the commandFinished() signal with the + \c error argument set to \c true is emitted for the command. Due + to timing issues, it is possible that the command had already + finished before the abort request reached the server, in which + case, the commandFinished() signal is emitted with the \c error + argument set to \c false. + + For all other commands that are affected by the abort(), no + signals are emitted. + + If you don't start further FTP commands directly after the + abort(), there won't be any scheduled commands and the done() + signal is emitted. + + \warning Some FTP servers, for example the BSD FTP daemon (version + 0.3), wrongly return a positive reply even when an abort has + occurred. For these servers the commandFinished() signal has its + error flag set to \c false, even though the command did not + complete successfully. + + \sa clearPendingCommands() +*/ +void QFtp::abort() +{ + if (d_func()->pending.isEmpty()) + return; + + clearPendingCommands(); + d_func()->pi.abort(); +} + +/*! + Returns the identifier of the FTP command that is being executed + or 0 if there is no command being executed. + + \sa currentCommand() +*/ +int QFtp::currentId() const +{ + if (d_func()->pending.isEmpty()) + return 0; + return d_func()->pending.first()->id; +} + +/*! + Returns the command type of the FTP command being executed or \c + None if there is no command being executed. + + \sa currentId() +*/ +QFtp::Command QFtp::currentCommand() const +{ + if (d_func()->pending.isEmpty()) + return None; + return d_func()->pending.first()->command; +} + +/*! + Returns the QIODevice pointer that is used by the FTP command to read data + from or store data to. If there is no current FTP command being executed or + if the command does not use an IO device, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the commandFinished() signal. + + \sa get() put() +*/ +QIODevice* QFtp::currentDevice() const +{ + if (d_func()->pending.isEmpty()) + return 0; + QFtpCommand *c = d_func()->pending.first(); + if (c->is_ba) + return 0; + return c->data.dev; +} + +/*! + Returns true if there are any commands scheduled that have not yet + been executed; otherwise returns false. + + The command that is being executed is \e not considered as a + scheduled command. + + \sa clearPendingCommands() currentId() currentCommand() +*/ +bool QFtp::hasPendingCommands() const +{ + return d_func()->pending.count() > 1; +} + +/*! + Deletes all pending commands from the list of scheduled commands. + This does not affect the command that is being executed. If you + want to stop this as well, use abort(). + + \sa hasPendingCommands() abort() +*/ +void QFtp::clearPendingCommands() +{ + // delete all entires except the first one + while (d_func()->pending.count() > 1) + delete d_func()->pending.takeLast(); +} + +/*! + Returns the current state of the object. When the state changes, + the stateChanged() signal is emitted. + + \sa State stateChanged() +*/ +QFtp::State QFtp::state() const +{ + return d_func()->state; +} + +/*! + Returns the last error that occurred. This is useful to find out + what went wrong when receiving a commandFinished() or a done() + signal with the \c error argument set to \c true. + + If you start a new command, the error status is reset to \c NoError. +*/ +QFtp::Error QFtp::error() const +{ + return d_func()->error; +} + +/*! + Returns a human-readable description of the last error that + occurred. This is useful for presenting a error message to the + user when receiving a commandFinished() or a done() signal with + the \c error argument set to \c true. + + The error string is often (but not always) the reply from the + server, so it is not always possible to translate the string. If + the message comes from Qt, the string has already passed through + tr(). +*/ +QString QFtp::errorString() const +{ + return d_func()->errorString; +} + +/*! \internal +*/ +void QFtpPrivate::_q_startNextCommand() +{ + Q_Q(QFtp); + if (pending.isEmpty()) + return; + QFtpCommand *c = pending.first(); + + error = QFtp::NoError; + errorString = QT_TRANSLATE_NOOP(QFtp, QLatin1String("Unknown error")); + + if (q->bytesAvailable()) + q->readAll(); // clear the data + emit q->commandStarted(c->id); + + // Proxy support, replace the Login argument in place, then fall + // through. + if (c->command == QFtp::Login && !proxyHost.isEmpty()) { + QString loginString = c->rawCmds.first().trimmed(); + loginString += QLatin1Char('@') + host; + if (port && port != 21) + loginString += QLatin1Char(':') + QString::number(port); + loginString += QLatin1String("\r\n"); + c->rawCmds[0] = loginString; + } + + if (c->command == QFtp::SetTransferMode) { + _q_piFinished(QLatin1String("Transfer mode set")); + } else if (c->command == QFtp::SetProxy) { + proxyHost = c->rawCmds[0]; + proxyPort = c->rawCmds[1].toUInt(); + c->rawCmds.clear(); + _q_piFinished(QLatin1String("Proxy set to ") + proxyHost + QLatin1Char(':') + QString::number(proxyPort)); + } else if (c->command == QFtp::ConnectToHost) { + if (!proxyHost.isEmpty()) { + host = c->rawCmds[0]; + port = c->rawCmds[1].toUInt(); + pi.connectToHost(proxyHost, proxyPort); + } else { + pi.connectToHost(c->rawCmds[0], c->rawCmds[1].toUInt()); + } + } else { + if (c->command == QFtp::Put) { + if (c->is_ba) { + pi.dtp.setData(c->data.ba); + pi.dtp.setBytesTotal(c->data.ba->size()); + } else if (c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly))) { + pi.dtp.setDevice(c->data.dev); + if (c->data.dev->isSequential()) { + pi.dtp.setBytesTotal(0); + pi.dtp.connect(c->data.dev, SIGNAL(readyRead()), SLOT(dataReadyRead())); + pi.dtp.connect(c->data.dev, SIGNAL(readChannelFinished()), SLOT(dataReadyRead())); + } else { + pi.dtp.setBytesTotal(c->data.dev->size()); + } + } + } else if (c->command == QFtp::Get) { + if (!c->is_ba && c->data.dev) { + pi.dtp.setDevice(c->data.dev); + } + } else if (c->command == QFtp::Close) { + state = QFtp::Closing; + emit q->stateChanged(state); + } + pi.sendCommands(c->rawCmds); + } +} + +/*! \internal +*/ +void QFtpPrivate::_q_piFinished(const QString&) +{ + if (pending.isEmpty()) + return; + QFtpCommand *c = pending.first(); + + if (c->command == QFtp::Close) { + // The order of in which the slots are called is arbitrary, so + // disconnect the SIGNAL-SIGNAL temporary to make sure that we + // don't get the commandFinished() signal before the stateChanged() + // signal. + if (state != QFtp::Unconnected) { + close_waitForStateChange = true; + return; + } + } + emit q_func()->commandFinished(c->id, false); + pending.removeFirst(); + + delete c; + + if (pending.isEmpty()) { + emit q_func()->done(false); + } else { + _q_startNextCommand(); + } +} + +/*! \internal +*/ +void QFtpPrivate::_q_piError(int errorCode, const QString &text) +{ + Q_Q(QFtp); + QFtpCommand *c = pending.first(); + + // non-fatal errors + if (c->command == QFtp::Get && pi.currentCommand().startsWith(QLatin1String("SIZE "))) { + pi.dtp.setBytesTotal(-1); + return; + } else if (c->command==QFtp::Put && pi.currentCommand().startsWith(QLatin1String("ALLO "))) { + return; + } + + error = QFtp::Error(errorCode); + switch (q->currentCommand()) { + case QFtp::ConnectToHost: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1")) + .arg(text); + break; + case QFtp::Login: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1")) + .arg(text); + break; + case QFtp::List: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1")) + .arg(text); + break; + case QFtp::Cd: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1")) + .arg(text); + break; + case QFtp::Get: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1")) + .arg(text); + break; + case QFtp::Put: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1")) + .arg(text); + break; + case QFtp::Remove: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1")) + .arg(text); + break; + case QFtp::Mkdir: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1")) + .arg(text); + break; + case QFtp::Rmdir: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1")) + .arg(text); + break; + default: + errorString = text; + break; + } + + pi.clearPendingCommands(); + q->clearPendingCommands(); + emit q->commandFinished(c->id, true); + + pending.removeFirst(); + delete c; + if (pending.isEmpty()) + emit q->done(true); + else + _q_startNextCommand(); +} + +/*! \internal +*/ +void QFtpPrivate::_q_piConnectState(int connectState) +{ + state = QFtp::State(connectState); + emit q_func()->stateChanged(state); + if (close_waitForStateChange) { + close_waitForStateChange = false; + _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed"))); + } +} + +/*! \internal +*/ +void QFtpPrivate::_q_piFtpReply(int code, const QString &text) +{ + if (q_func()->currentCommand() == QFtp::RawCommand) { + pi.rawCommand = true; + emit q_func()->rawCommandReply(code, text); + } +} + +/*! + Destructor. +*/ +QFtp::~QFtp() +{ + abort(); + close(); +} + +QT_END_NAMESPACE + +#include "qftp.moc" + +#include "moc_qftp.cpp" + +#endif // QT_NO_FTP diff --git a/src/network/access/qftp.h b/src/network/access/qftp.h new file mode 100644 index 0000000..ba759e0 --- /dev/null +++ b/src/network/access/qftp.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFTP_H +#define QFTP_H + +#include <QtCore/qstring.h> +#include <QtNetwork/qurlinfo.h> +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_FTP + +class QFtpPrivate; + +class Q_NETWORK_EXPORT QFtp : public QObject +{ + Q_OBJECT + +public: + explicit QFtp(QObject *parent = 0); + virtual ~QFtp(); + + enum State { + Unconnected, + HostLookup, + Connecting, + Connected, + LoggedIn, + Closing + }; + enum Error { + NoError, + UnknownError, + HostNotFound, + ConnectionRefused, + NotConnected + }; + enum Command { + None, + SetTransferMode, + SetProxy, + ConnectToHost, + Login, + Close, + List, + Cd, + Get, + Put, + Remove, + Mkdir, + Rmdir, + Rename, + RawCommand + }; + enum TransferMode { + Active, + Passive + }; + enum TransferType { + Binary, + Ascii + }; + + int setProxy(const QString &host, quint16 port); + int connectToHost(const QString &host, quint16 port=21); + int login(const QString &user = QString(), const QString &password = QString()); + int close(); + int setTransferMode(TransferMode mode); + int list(const QString &dir = QString()); + int cd(const QString &dir); + int get(const QString &file, QIODevice *dev=0, TransferType type = Binary); + int put(const QByteArray &data, const QString &file, TransferType type = Binary); + int put(QIODevice *dev, const QString &file, TransferType type = Binary); + int remove(const QString &file); + int mkdir(const QString &dir); + int rmdir(const QString &dir); + int rename(const QString &oldname, const QString &newname); + + int rawCommand(const QString &command); + + qint64 bytesAvailable() const; + qint64 read(char *data, qint64 maxlen); +#ifdef QT3_SUPPORT + inline QT3_SUPPORT qint64 readBlock(char *data, quint64 maxlen) + { return read(data, qint64(maxlen)); } +#endif + QByteArray readAll(); + + int currentId() const; + QIODevice* currentDevice() const; + Command currentCommand() const; + bool hasPendingCommands() const; + void clearPendingCommands(); + + State state() const; + + Error error() const; + QString errorString() const; + +public Q_SLOTS: + void abort(); + +Q_SIGNALS: + void stateChanged(int); + void listInfo(const QUrlInfo&); + void readyRead(); + void dataTransferProgress(qint64, qint64); + void rawCommandReply(int, const QString&); + + void commandStarted(int); + void commandFinished(int, bool); + void done(bool); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QFtp(QObject *parent, const char *name); +#endif + +private: + Q_DISABLE_COPY(QFtp) + Q_DECLARE_PRIVATE(QFtp) + + Q_PRIVATE_SLOT(d_func(), void _q_startNextCommand()) + Q_PRIVATE_SLOT(d_func(), void _q_piFinished(const QString&)) + Q_PRIVATE_SLOT(d_func(), void _q_piError(int, const QString&)) + Q_PRIVATE_SLOT(d_func(), void _q_piConnectState(int)) + Q_PRIVATE_SLOT(d_func(), void _q_piFtpReply(int, const QString&)) +}; + +#endif // QT_NO_FTP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFTP_H diff --git a/src/network/access/qhttp.cpp b/src/network/access/qhttp.cpp new file mode 100644 index 0000000..0141ae2 --- /dev/null +++ b/src/network/access/qhttp.cpp @@ -0,0 +1,3120 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QHTTP_DEBUG + +#include <qplatformdefs.h> +#include "qhttp.h" + +#ifndef QT_NO_HTTP +# include "private/qobject_p.h" +# include "qtcpsocket.h" +# include "qsslsocket.h" +# include "qtextstream.h" +# include "qmap.h" +# include "qlist.h" +# include "qstring.h" +# include "qstringlist.h" +# include "qbuffer.h" +# include "private/qringbuffer_p.h" +# include "qcoreevent.h" +# include "qurl.h" +# include "qnetworkproxy.h" +# include "qauthenticator.h" +# include "qauthenticator_p.h" +# include "qdebug.h" +# include "qtimer.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_HTTP + +class QHttpNormalRequest; +class QHttpRequest +{ +public: + QHttpRequest() : finished(false) + { id = idCounter.fetchAndAddRelaxed(1); } + virtual ~QHttpRequest() + { } + + virtual void start(QHttp *) = 0; + virtual bool hasRequestHeader(); + virtual QHttpRequestHeader requestHeader(); + + virtual QIODevice *sourceDevice() = 0; + virtual QIODevice *destinationDevice() = 0; + + int id; + bool finished; + +private: + static QBasicAtomicInt idCounter; +}; + +class QHttpPrivate : public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QHttp) + + inline QHttpPrivate() + : socket(0), reconnectAttempts(2), + deleteSocket(0), state(QHttp::Unconnected), + error(QHttp::NoError), port(0), mode(QHttp::ConnectionModeHttp), + toDevice(0), postDevice(0), bytesDone(0), chunkedSize(-1), + repost(false), pendingPost(false) + { + } + + inline ~QHttpPrivate() + { + while (!pending.isEmpty()) + delete pending.takeFirst(); + + if (deleteSocket) + delete socket; + } + + // private slots + void _q_startNextRequest(); + void _q_slotReadyRead(); + void _q_slotConnected(); + void _q_slotError(QAbstractSocket::SocketError); + void _q_slotClosed(); + void _q_slotBytesWritten(qint64 numBytes); + void _q_slotDoFinished(); + void _q_slotSendRequest(); + void _q_continuePost(); + + int addRequest(QHttpNormalRequest *); + int addRequest(QHttpRequest *); + void finishedWithSuccess(); + void finishedWithError(const QString &detail, int errorCode); + + void init(); + void setState(int); + void closeConn(); + void setSock(QTcpSocket *sock); + + QTcpSocket *socket; + int reconnectAttempts; + bool deleteSocket; + QList<QHttpRequest *> pending; + + QHttp::State state; + QHttp::Error error; + QString errorString; + + QString hostName; + quint16 port; + QHttp::ConnectionMode mode; + + QByteArray buffer; + QIODevice *toDevice; + QIODevice *postDevice; + + qint64 bytesDone; + qint64 bytesTotal; + qint64 chunkedSize; + + QHttpRequestHeader header; + + bool readHeader; + QString headerStr; + QHttpResponseHeader response; + + QRingBuffer rba; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QAuthenticator proxyAuthenticator; +#endif + QAuthenticator authenticator; + bool repost; + bool hasFinishedWithError; + bool pendingPost; + QTimer post100ContinueTimer; +}; + +QBasicAtomicInt QHttpRequest::idCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +bool QHttpRequest::hasRequestHeader() +{ + return false; +} + +QHttpRequestHeader QHttpRequest::requestHeader() +{ + return QHttpRequestHeader(); +} + +/**************************************************** + * + * QHttpNormalRequest + * + ****************************************************/ + +class QHttpNormalRequest : public QHttpRequest +{ +public: + QHttpNormalRequest(const QHttpRequestHeader &h, QIODevice *d, QIODevice *t) : + header(h), to(t) + { + is_ba = false; + data.dev = d; + } + + QHttpNormalRequest(const QHttpRequestHeader &h, QByteArray *d, QIODevice *t) : + header(h), to(t) + { + is_ba = true; + data.ba = d; + } + + ~QHttpNormalRequest() + { + if (is_ba) + delete data.ba; + } + + void start(QHttp *); + bool hasRequestHeader(); + QHttpRequestHeader requestHeader(); + inline void setRequestHeader(const QHttpRequestHeader &h) { header = h; } + + QIODevice *sourceDevice(); + QIODevice *destinationDevice(); + +protected: + QHttpRequestHeader header; + +private: + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; + QIODevice *to; +}; + +void QHttpNormalRequest::start(QHttp *http) +{ + if (!http->d_func()->socket) + http->d_func()->setSock(0); + http->d_func()->header = header; + + if (is_ba) { + http->d_func()->buffer = *data.ba; + if (http->d_func()->buffer.size() >= 0) + http->d_func()->header.setContentLength(http->d_func()->buffer.size()); + + http->d_func()->postDevice = 0; + } else { + http->d_func()->buffer = QByteArray(); + + if (data.dev && (data.dev->isOpen() || data.dev->open(QIODevice::ReadOnly))) { + http->d_func()->postDevice = data.dev; + if (http->d_func()->postDevice->size() >= 0) + http->d_func()->header.setContentLength(http->d_func()->postDevice->size()); + } else { + http->d_func()->postDevice = 0; + } + } + + if (to && (to->isOpen() || to->open(QIODevice::WriteOnly))) + http->d_func()->toDevice = to; + else + http->d_func()->toDevice = 0; + + http->d_func()->reconnectAttempts = 2; + http->d_func()->_q_slotSendRequest(); +} + +bool QHttpNormalRequest::hasRequestHeader() +{ + return true; +} + +QHttpRequestHeader QHttpNormalRequest::requestHeader() +{ + return header; +} + +QIODevice *QHttpNormalRequest::sourceDevice() +{ + if (is_ba) + return 0; + return data.dev; +} + +QIODevice *QHttpNormalRequest::destinationDevice() +{ + return to; +} + +/**************************************************** + * + * QHttpPGHRequest + * (like a QHttpNormalRequest, but for the convenience + * functions put(), get() and head() -- i.e. set the + * host header field correctly before sending the + * request) + * + ****************************************************/ + +class QHttpPGHRequest : public QHttpNormalRequest +{ +public: + QHttpPGHRequest(const QHttpRequestHeader &h, QIODevice *d, QIODevice *t) : + QHttpNormalRequest(h, d, t) + { } + + QHttpPGHRequest(const QHttpRequestHeader &h, QByteArray *d, QIODevice *t) : + QHttpNormalRequest(h, d, t) + { } + + ~QHttpPGHRequest() + { } + + void start(QHttp *); +}; + +void QHttpPGHRequest::start(QHttp *http) +{ + if (http->d_func()->port && http->d_func()->port != 80) + header.setValue(QLatin1String("Host"), http->d_func()->hostName + QLatin1Char(':') + QString::number(http->d_func()->port)); + else + header.setValue(QLatin1String("Host"), http->d_func()->hostName); + QHttpNormalRequest::start(http); +} + +/**************************************************** + * + * QHttpSetHostRequest + * + ****************************************************/ + +class QHttpSetHostRequest : public QHttpRequest +{ +public: + QHttpSetHostRequest(const QString &h, quint16 p, QHttp::ConnectionMode m) + : hostName(h), port(p), mode(m) + { } + + void start(QHttp *); + + QIODevice *sourceDevice() + { return 0; } + QIODevice *destinationDevice() + { return 0; } + +private: + QString hostName; + quint16 port; + QHttp::ConnectionMode mode; +}; + +void QHttpSetHostRequest::start(QHttp *http) +{ + http->d_func()->hostName = hostName; + http->d_func()->port = port; + http->d_func()->mode = mode; + +#ifdef QT_NO_OPENSSL + if (mode == QHttp::ConnectionModeHttps) { + // SSL requested but no SSL support compiled in + http->d_func()->finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTPS connection requested but SSL support not compiled in")), + QHttp::UnknownError); + return; + } +#endif + + http->d_func()->finishedWithSuccess(); +} + +/**************************************************** + * + * QHttpSetUserRequest + * + ****************************************************/ + +class QHttpSetUserRequest : public QHttpRequest +{ +public: + QHttpSetUserRequest(const QString &userName, const QString &password) : + user(userName), pass(password) + { } + + void start(QHttp *); + + QIODevice *sourceDevice() + { return 0; } + QIODevice *destinationDevice() + { return 0; } + +private: + QString user; + QString pass; +}; + +void QHttpSetUserRequest::start(QHttp *http) +{ + http->d_func()->authenticator.setUser(user); + http->d_func()->authenticator.setPassword(pass); + http->d_func()->finishedWithSuccess(); +} + +#ifndef QT_NO_NETWORKPROXY + +/**************************************************** + * + * QHttpSetProxyRequest + * + ****************************************************/ + +class QHttpSetProxyRequest : public QHttpRequest +{ +public: + inline QHttpSetProxyRequest(const QNetworkProxy &proxy) + { + this->proxy = proxy; + } + + inline void start(QHttp *http) + { + http->d_func()->proxy = proxy; + QString user = proxy.user(); + if (!user.isEmpty()) + http->d_func()->proxyAuthenticator.setUser(user); + QString password = proxy.password(); + if (!password.isEmpty()) + http->d_func()->proxyAuthenticator.setPassword(password); + http->d_func()->finishedWithSuccess(); + } + + inline QIODevice *sourceDevice() + { return 0; } + inline QIODevice *destinationDevice() + { return 0; } +private: + QNetworkProxy proxy; +}; + +#endif // QT_NO_NETWORKPROXY + +/**************************************************** + * + * QHttpSetSocketRequest + * + ****************************************************/ + +class QHttpSetSocketRequest : public QHttpRequest +{ +public: + QHttpSetSocketRequest(QTcpSocket *s) : socket(s) + { } + + void start(QHttp *); + + QIODevice *sourceDevice() + { return 0; } + QIODevice *destinationDevice() + { return 0; } + +private: + QTcpSocket *socket; +}; + +void QHttpSetSocketRequest::start(QHttp *http) +{ + http->d_func()->setSock(socket); + http->d_func()->finishedWithSuccess(); +} + +/**************************************************** + * + * QHttpCloseRequest + * + ****************************************************/ + +class QHttpCloseRequest : public QHttpRequest +{ +public: + QHttpCloseRequest() + { } + void start(QHttp *); + + QIODevice *sourceDevice() + { return 0; } + QIODevice *destinationDevice() + { return 0; } +}; + +void QHttpCloseRequest::start(QHttp *http) +{ + http->d_func()->closeConn(); +} + +class QHttpHeaderPrivate +{ + Q_DECLARE_PUBLIC(QHttpHeader) +public: + inline virtual ~QHttpHeaderPrivate() {} + + QList<QPair<QString, QString> > values; + bool valid; + QHttpHeader *q_ptr; +}; + +/**************************************************** + * + * QHttpHeader + * + ****************************************************/ + +/*! + \class QHttpHeader + \brief The QHttpHeader class contains header information for HTTP. + + \ingroup io + \inmodule QtNetwork + + In most cases you should use the more specialized derivatives of + this class, QHttpResponseHeader and QHttpRequestHeader, rather + than directly using QHttpHeader. + + QHttpHeader provides the HTTP header fields. A HTTP header field + consists of a name followed by a colon, a single space, and the + field value. (See RFC 1945.) Field names are case-insensitive. A + typical header field looks like this: + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 0 + + In the API the header field name is called the "key" and the + content is called the "value". You can get and set a header + field's value by using its key with value() and setValue(), e.g. + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 1 + + Some fields are so common that getters and setters are provided + for them as a convenient alternative to using \l value() and + \l setValue(), e.g. contentLength() and contentType(), + setContentLength() and setContentType(). + + Each header key has a \e single value associated with it. If you + set the value for a key which already exists the previous value + will be discarded. + + \sa QHttpRequestHeader QHttpResponseHeader +*/ + +/*! + \fn int QHttpHeader::majorVersion() const + + Returns the major protocol-version of the HTTP header. +*/ + +/*! + \fn int QHttpHeader::minorVersion() const + + Returns the minor protocol-version of the HTTP header. +*/ + +/*! + Constructs an empty HTTP header. +*/ +QHttpHeader::QHttpHeader() + : d_ptr(new QHttpHeaderPrivate) +{ + Q_D(QHttpHeader); + d->q_ptr = this; + d->valid = true; +} + +/*! + Constructs a copy of \a header. +*/ +QHttpHeader::QHttpHeader(const QHttpHeader &header) + : d_ptr(new QHttpHeaderPrivate) +{ + Q_D(QHttpHeader); + d->q_ptr = this; + d->valid = header.d_func()->valid; + d->values = header.d_func()->values; +} + +/*! + Constructs a HTTP header for \a str. + + This constructor parses the string \a str for header fields and + adds this information. The \a str should consist of one or more + "\r\n" delimited lines; each of these lines should have the format + key, colon, space, value. +*/ +QHttpHeader::QHttpHeader(const QString &str) + : d_ptr(new QHttpHeaderPrivate) +{ + Q_D(QHttpHeader); + d->q_ptr = this; + d->valid = true; + parse(str); +} + +/*! \internal + */ +QHttpHeader::QHttpHeader(QHttpHeaderPrivate &dd, const QString &str) + : d_ptr(&dd) +{ + Q_D(QHttpHeader); + d->q_ptr = this; + d->valid = true; + if (!str.isEmpty()) + parse(str); +} + +/*! \internal + */ +QHttpHeader::QHttpHeader(QHttpHeaderPrivate &dd, const QHttpHeader &header) + : d_ptr(&dd) +{ + Q_D(QHttpHeader); + d->q_ptr = this; + d->valid = header.d_func()->valid; + d->values = header.d_func()->values; +} +/*! + Destructor. +*/ +QHttpHeader::~QHttpHeader() +{ + delete d_ptr; +} + +/*! + Assigns \a h and returns a reference to this http header. +*/ +QHttpHeader &QHttpHeader::operator=(const QHttpHeader &h) +{ + Q_D(QHttpHeader); + d->values = h.d_func()->values; + d->valid = h.d_func()->valid; + return *this; +} + +/*! + Returns true if the HTTP header is valid; otherwise returns false. + + A QHttpHeader is invalid if it was created by parsing a malformed string. +*/ +bool QHttpHeader::isValid() const +{ + Q_D(const QHttpHeader); + return d->valid; +} + +/*! \internal + Parses the HTTP header string \a str for header fields and adds + the keys/values it finds. If the string is not parsed successfully + the QHttpHeader becomes \link isValid() invalid\endlink. + + Returns true if \a str was successfully parsed; otherwise returns false. + + \sa toString() +*/ +bool QHttpHeader::parse(const QString &str) +{ + Q_D(QHttpHeader); + QStringList lst; + int pos = str.indexOf(QLatin1Char('\n')); + if (pos > 0 && str.at(pos - 1) == QLatin1Char('\r')) + lst = str.trimmed().split(QLatin1String("\r\n")); + else + lst = str.trimmed().split(QLatin1String("\n")); + lst.removeAll(QString()); // No empties + + if (lst.isEmpty()) + return true; + + QStringList lines; + QStringList::Iterator it = lst.begin(); + for (; it != lst.end(); ++it) { + if (!(*it).isEmpty()) { + if ((*it)[0].isSpace()) { + if (!lines.isEmpty()) { + lines.last() += QLatin1Char(' '); + lines.last() += (*it).trimmed(); + } + } else { + lines.append((*it)); + } + } + } + + int number = 0; + it = lines.begin(); + for (; it != lines.end(); ++it) { + if (!parseLine(*it, number++)) { + d->valid = false; + return false; + } + } + return true; +} + +/*! \internal +*/ +void QHttpHeader::setValid(bool v) +{ + Q_D(QHttpHeader); + d->valid = v; +} + +/*! + Returns the first value for the entry with the given \a key. If no entry + has this \a key, an empty string is returned. + + \sa setValue() removeValue() hasKey() keys() +*/ +QString QHttpHeader::value(const QString &key) const +{ + Q_D(const QHttpHeader); + QString lowercaseKey = key.toLower(); + QList<QPair<QString, QString> >::ConstIterator it = d->values.constBegin(); + while (it != d->values.constEnd()) { + if ((*it).first.toLower() == lowercaseKey) + return (*it).second; + ++it; + } + return QString(); +} + +/*! + Returns all the entries with the given \a key. If no entry + has this \a key, an empty string list is returned. +*/ +QStringList QHttpHeader::allValues(const QString &key) const +{ + Q_D(const QHttpHeader); + QString lowercaseKey = key.toLower(); + QStringList valueList; + QList<QPair<QString, QString> >::ConstIterator it = d->values.constBegin(); + while (it != d->values.constEnd()) { + if ((*it).first.toLower() == lowercaseKey) + valueList.append((*it).second); + ++it; + } + return valueList; +} + +/*! + Returns a list of the keys in the HTTP header. + + \sa hasKey() +*/ +QStringList QHttpHeader::keys() const +{ + Q_D(const QHttpHeader); + QStringList keyList; + QSet<QString> seenKeys; + QList<QPair<QString, QString> >::ConstIterator it = d->values.constBegin(); + while (it != d->values.constEnd()) { + const QString &key = (*it).first; + QString lowercaseKey = key.toLower(); + if (!seenKeys.contains(lowercaseKey)) { + keyList.append(key); + seenKeys.insert(lowercaseKey); + } + ++it; + } + return keyList; +} + +/*! + Returns true if the HTTP header has an entry with the given \a + key; otherwise returns false. + + \sa value() setValue() keys() +*/ +bool QHttpHeader::hasKey(const QString &key) const +{ + Q_D(const QHttpHeader); + QString lowercaseKey = key.toLower(); + QList<QPair<QString, QString> >::ConstIterator it = d->values.constBegin(); + while (it != d->values.constEnd()) { + if ((*it).first.toLower() == lowercaseKey) + return true; + ++it; + } + return false; +} + +/*! + Sets the value of the entry with the \a key to \a value. + + If no entry with \a key exists, a new entry with the given \a key + and \a value is created. If an entry with the \a key already + exists, the first value is discarded and replaced with the given + \a value. + + \sa value() hasKey() removeValue() +*/ +void QHttpHeader::setValue(const QString &key, const QString &value) +{ + Q_D(QHttpHeader); + QString lowercaseKey = key.toLower(); + QList<QPair<QString, QString> >::Iterator it = d->values.begin(); + while (it != d->values.end()) { + if ((*it).first.toLower() == lowercaseKey) { + (*it).second = value; + return; + } + ++it; + } + // not found so add + addValue(key, value); +} + +/*! + Sets the header entries to be the list of key value pairs in \a values. +*/ +void QHttpHeader::setValues(const QList<QPair<QString, QString> > &values) +{ + Q_D(QHttpHeader); + d->values = values; +} + +/*! + Adds a new entry with the \a key and \a value. +*/ +void QHttpHeader::addValue(const QString &key, const QString &value) +{ + Q_D(QHttpHeader); + d->values.append(qMakePair(key, value)); +} + +/*! + Returns all the entries in the header. +*/ +QList<QPair<QString, QString> > QHttpHeader::values() const +{ + Q_D(const QHttpHeader); + return d->values; +} + +/*! + Removes the entry with the key \a key from the HTTP header. + + \sa value() setValue() +*/ +void QHttpHeader::removeValue(const QString &key) +{ + Q_D(QHttpHeader); + QString lowercaseKey = key.toLower(); + QList<QPair<QString, QString> >::Iterator it = d->values.begin(); + while (it != d->values.end()) { + if ((*it).first.toLower() == lowercaseKey) { + d->values.erase(it); + return; + } + ++it; + } +} + +/*! + Removes all the entries with the key \a key from the HTTP header. +*/ +void QHttpHeader::removeAllValues(const QString &key) +{ + Q_D(QHttpHeader); + QString lowercaseKey = key.toLower(); + QList<QPair<QString, QString> >::Iterator it = d->values.begin(); + while (it != d->values.end()) { + if ((*it).first.toLower() == lowercaseKey) { + it = d->values.erase(it); + continue; + } + ++it; + } +} + +/*! \internal + Parses the single HTTP header line \a line which has the format + key, colon, space, value, and adds key/value to the headers. The + linenumber is \a number. Returns true if the line was successfully + parsed and the key/value added; otherwise returns false. + + \sa parse() +*/ +bool QHttpHeader::parseLine(const QString &line, int) +{ + int i = line.indexOf(QLatin1Char(':')); + if (i == -1) + return false; + + addValue(line.left(i).trimmed(), line.mid(i + 1).trimmed()); + + return true; +} + +/*! + Returns a string representation of the HTTP header. + + The string is suitable for use by the constructor that takes a + QString. It consists of lines with the format: key, colon, space, + value, "\r\n". +*/ +QString QHttpHeader::toString() const +{ + Q_D(const QHttpHeader); + if (!isValid()) + return QLatin1String(""); + + QString ret = QLatin1String(""); + + QList<QPair<QString, QString> >::ConstIterator it = d->values.constBegin(); + while (it != d->values.constEnd()) { + ret += (*it).first + QLatin1String(": ") + (*it).second + QLatin1String("\r\n"); + ++it; + } + return ret; +} + +/*! + Returns true if the header has an entry for the special HTTP + header field \c content-length; otherwise returns false. + + \sa contentLength() setContentLength() +*/ +bool QHttpHeader::hasContentLength() const +{ + return hasKey(QLatin1String("content-length")); +} + +/*! + Returns the value of the special HTTP header field \c + content-length. + + \sa setContentLength() hasContentLength() +*/ +uint QHttpHeader::contentLength() const +{ + return value(QLatin1String("content-length")).toUInt(); +} + +/*! + Sets the value of the special HTTP header field \c content-length + to \a len. + + \sa contentLength() hasContentLength() +*/ +void QHttpHeader::setContentLength(int len) +{ + setValue(QLatin1String("content-length"), QString::number(len)); +} + +/*! + Returns true if the header has an entry for the the special HTTP + header field \c content-type; otherwise returns false. + + \sa contentType() setContentType() +*/ +bool QHttpHeader::hasContentType() const +{ + return hasKey(QLatin1String("content-type")); +} + +/*! + Returns the value of the special HTTP header field \c content-type. + + \sa setContentType() hasContentType() +*/ +QString QHttpHeader::contentType() const +{ + QString type = value(QLatin1String("content-type")); + if (type.isEmpty()) + return QString(); + + int pos = type.indexOf(QLatin1Char(';')); + if (pos == -1) + return type; + + return type.left(pos).trimmed(); +} + +/*! + Sets the value of the special HTTP header field \c content-type to + \a type. + + \sa contentType() hasContentType() +*/ +void QHttpHeader::setContentType(const QString &type) +{ + setValue(QLatin1String("content-type"), type); +} + +class QHttpResponseHeaderPrivate : public QHttpHeaderPrivate +{ + Q_DECLARE_PUBLIC(QHttpResponseHeader) +public: + int statCode; + QString reasonPhr; + int majVer; + int minVer; +}; + +/**************************************************** + * + * QHttpResponseHeader + * + ****************************************************/ + +/*! + \class QHttpResponseHeader + \brief The QHttpResponseHeader class contains response header information for HTTP. + + \ingroup io + \inmodule QtNetwork + + This class is used by the QHttp class to report the header + information that the client received from the server. + + HTTP responses have a status code that indicates the status of the + response. This code is a 3-digit integer result code (for details + see to RFC 1945). In addition to the status code, you can also + specify a human-readable text that describes the reason for the + code ("reason phrase"). This class allows you to get the status + code and the reason phrase. + + \sa QHttpRequestHeader, QHttp, {HTTP Example} +*/ + +/*! + Constructs an empty HTTP response header. +*/ +QHttpResponseHeader::QHttpResponseHeader() + : QHttpHeader(*new QHttpResponseHeaderPrivate) +{ + setValid(false); +} + +/*! + Constructs a copy of \a header. +*/ +QHttpResponseHeader::QHttpResponseHeader(const QHttpResponseHeader &header) + : QHttpHeader(*new QHttpResponseHeaderPrivate, header) +{ + Q_D(QHttpResponseHeader); + d->statCode = header.d_func()->statCode; + d->reasonPhr = header.d_func()->reasonPhr; + d->majVer = header.d_func()->majVer; + d->minVer = header.d_func()->minVer; +} + +/*! + Copies the contents of \a header into this QHttpResponseHeader. +*/ +QHttpResponseHeader &QHttpResponseHeader::operator=(const QHttpResponseHeader &header) +{ + Q_D(QHttpResponseHeader); + QHttpHeader::operator=(header); + d->statCode = header.d_func()->statCode; + d->reasonPhr = header.d_func()->reasonPhr; + d->majVer = header.d_func()->majVer; + d->minVer = header.d_func()->minVer; + return *this; +} + +/*! + Constructs a HTTP response header from the string \a str. The + string is parsed and the information is set. The \a str should + consist of one or more "\r\n" delimited lines; the first line should be the + status-line (format: HTTP-version, space, status-code, space, + reason-phrase); each of remaining lines should have the format key, colon, + space, value. +*/ +QHttpResponseHeader::QHttpResponseHeader(const QString &str) + : QHttpHeader(*new QHttpResponseHeaderPrivate) +{ + parse(str); +} + +/*! + \since 4.1 + + Constructs a QHttpResponseHeader, setting the status code to \a code, the + reason phrase to \a text and the protocol-version to \a majorVer and \a + minorVer. + + \sa statusCode() reasonPhrase() majorVersion() minorVersion() +*/ +QHttpResponseHeader::QHttpResponseHeader(int code, const QString &text, int majorVer, int minorVer) + : QHttpHeader(*new QHttpResponseHeaderPrivate) +{ + setStatusLine(code, text, majorVer, minorVer); +} + +/*! + \since 4.1 + + Sets the status code to \a code, the reason phrase to \a text and + the protocol-version to \a majorVer and \a minorVer. + + \sa statusCode() reasonPhrase() majorVersion() minorVersion() +*/ +void QHttpResponseHeader::setStatusLine(int code, const QString &text, int majorVer, int minorVer) +{ + Q_D(QHttpResponseHeader); + setValid(true); + d->statCode = code; + d->reasonPhr = text; + d->majVer = majorVer; + d->minVer = minorVer; +} + +/*! + Returns the status code of the HTTP response header. + + \sa reasonPhrase() majorVersion() minorVersion() +*/ +int QHttpResponseHeader::statusCode() const +{ + Q_D(const QHttpResponseHeader); + return d->statCode; +} + +/*! + Returns the reason phrase of the HTTP response header. + + \sa statusCode() majorVersion() minorVersion() +*/ +QString QHttpResponseHeader::reasonPhrase() const +{ + Q_D(const QHttpResponseHeader); + return d->reasonPhr; +} + +/*! + Returns the major protocol-version of the HTTP response header. + + \sa minorVersion() statusCode() reasonPhrase() +*/ +int QHttpResponseHeader::majorVersion() const +{ + Q_D(const QHttpResponseHeader); + return d->majVer; +} + +/*! + Returns the minor protocol-version of the HTTP response header. + + \sa majorVersion() statusCode() reasonPhrase() +*/ +int QHttpResponseHeader::minorVersion() const +{ + Q_D(const QHttpResponseHeader); + return d->minVer; +} + +/*! \reimp +*/ +bool QHttpResponseHeader::parseLine(const QString &line, int number) +{ + Q_D(QHttpResponseHeader); + if (number != 0) + return QHttpHeader::parseLine(line, number); + + QString l = line.simplified(); + if (l.length() < 10) + return false; + + if (l.left(5) == QLatin1String("HTTP/") && l[5].isDigit() && l[6] == QLatin1Char('.') && + l[7].isDigit() && l[8] == QLatin1Char(' ') && l[9].isDigit()) { + d->majVer = l[5].toLatin1() - '0'; + d->minVer = l[7].toLatin1() - '0'; + + int pos = l.indexOf(QLatin1Char(' '), 9); + if (pos != -1) { + d->reasonPhr = l.mid(pos + 1); + d->statCode = l.mid(9, pos - 9).toInt(); + } else { + d->statCode = l.mid(9).toInt(); + d->reasonPhr.clear(); + } + } else { + return false; + } + + return true; +} + +/*! \reimp +*/ +QString QHttpResponseHeader::toString() const +{ + Q_D(const QHttpResponseHeader); + QString ret(QLatin1String("HTTP/%1.%2 %3 %4\r\n%5\r\n")); + return ret.arg(d->majVer).arg(d->minVer).arg(d->statCode).arg(d->reasonPhr).arg(QHttpHeader::toString()); +} + +class QHttpRequestHeaderPrivate : public QHttpHeaderPrivate +{ + Q_DECLARE_PUBLIC(QHttpRequestHeader) +public: + QString m; + QString p; + int majVer; + int minVer; +}; + +/**************************************************** + * + * QHttpRequestHeader + * + ****************************************************/ + +/*! + \class QHttpRequestHeader + \brief The QHttpRequestHeader class contains request header information for HTTP. + + \ingroup io + \inmodule QtNetwork + + This class is used in the QHttp class to report the header + information if the client requests something from the server. + + HTTP requests have a method which describes the request's action. + The most common requests are "GET" and "POST". In addition to the + request method the header also includes a request-URI to specify + the location for the method to use. + + The method, request-URI and protocol-version can be set using a + constructor or later using setRequest(). The values can be + obtained using method(), path(), majorVersion() and + minorVersion(). + + Note that the request-URI must be in the format expected by the + HTTP server. That is, all reserved characters must be encoded in + %HH (where HH are two hexadecimal digits). See + QUrl::toPercentEncoding() for more information. + + Important inherited functions: setValue() and value(). + + \sa QHttpResponseHeader QHttp +*/ + +/*! + Constructs an empty HTTP request header. +*/ +QHttpRequestHeader::QHttpRequestHeader() + : QHttpHeader(*new QHttpRequestHeaderPrivate) +{ + setValid(false); +} + +/*! + Constructs a HTTP request header for the method \a method, the + request-URI \a path and the protocol-version \a majorVer and \a + minorVer. The \a path argument must be properly encoded for an + HTTP request. +*/ +QHttpRequestHeader::QHttpRequestHeader(const QString &method, const QString &path, int majorVer, int minorVer) + : QHttpHeader(*new QHttpRequestHeaderPrivate) +{ + Q_D(QHttpRequestHeader); + d->m = method; + d->p = path; + d->majVer = majorVer; + d->minVer = minorVer; +} + +/*! + Constructs a copy of \a header. +*/ +QHttpRequestHeader::QHttpRequestHeader(const QHttpRequestHeader &header) + : QHttpHeader(*new QHttpRequestHeaderPrivate, header) +{ + Q_D(QHttpRequestHeader); + d->m = header.d_func()->m; + d->p = header.d_func()->p; + d->majVer = header.d_func()->majVer; + d->minVer = header.d_func()->minVer; +} + +/*! + Copies the content of \a header into this QHttpRequestHeader +*/ +QHttpRequestHeader &QHttpRequestHeader::operator=(const QHttpRequestHeader &header) +{ + Q_D(QHttpRequestHeader); + QHttpHeader::operator=(header); + d->m = header.d_func()->m; + d->p = header.d_func()->p; + d->majVer = header.d_func()->majVer; + d->minVer = header.d_func()->minVer; + return *this; +} + +/*! + Constructs a HTTP request header from the string \a str. The \a + str should consist of one or more "\r\n" delimited lines; the first line + should be the request-line (format: method, space, request-URI, space + HTTP-version); each of the remaining lines should have the format key, + colon, space, value. +*/ +QHttpRequestHeader::QHttpRequestHeader(const QString &str) + : QHttpHeader(*new QHttpRequestHeaderPrivate) +{ + parse(str); +} + +/*! + This function sets the request method to \a method, the + request-URI to \a path and the protocol-version to \a majorVer and + \a minorVer. The \a path argument must be properly encoded for an + HTTP request. + + \sa method() path() majorVersion() minorVersion() +*/ +void QHttpRequestHeader::setRequest(const QString &method, const QString &path, int majorVer, int minorVer) +{ + Q_D(QHttpRequestHeader); + setValid(true); + d->m = method; + d->p = path; + d->majVer = majorVer; + d->minVer = minorVer; +} + +/*! + Returns the method of the HTTP request header. + + \sa path() majorVersion() minorVersion() setRequest() +*/ +QString QHttpRequestHeader::method() const +{ + Q_D(const QHttpRequestHeader); + return d->m; +} + +/*! + Returns the request-URI of the HTTP request header. + + \sa method() majorVersion() minorVersion() setRequest() +*/ +QString QHttpRequestHeader::path() const +{ + Q_D(const QHttpRequestHeader); + return d->p; +} + +/*! + Returns the major protocol-version of the HTTP request header. + + \sa minorVersion() method() path() setRequest() +*/ +int QHttpRequestHeader::majorVersion() const +{ + Q_D(const QHttpRequestHeader); + return d->majVer; +} + +/*! + Returns the minor protocol-version of the HTTP request header. + + \sa majorVersion() method() path() setRequest() +*/ +int QHttpRequestHeader::minorVersion() const +{ + Q_D(const QHttpRequestHeader); + return d->minVer; +} + +/*! \reimp +*/ +bool QHttpRequestHeader::parseLine(const QString &line, int number) +{ + Q_D(QHttpRequestHeader); + if (number != 0) + return QHttpHeader::parseLine(line, number); + + QStringList lst = line.simplified().split(QLatin1String(" ")); + if (lst.count() > 0) { + d->m = lst[0]; + if (lst.count() > 1) { + d->p = lst[1]; + if (lst.count() > 2) { + QString v = lst[2]; + if (v.length() >= 8 && v.left(5) == QLatin1String("HTTP/") && + v[5].isDigit() && v[6] == QLatin1Char('.') && v[7].isDigit()) { + d->majVer = v[5].toLatin1() - '0'; + d->minVer = v[7].toLatin1() - '0'; + return true; + } + } + } + } + + return false; +} + +/*! \reimp +*/ +QString QHttpRequestHeader::toString() const +{ + Q_D(const QHttpRequestHeader); + QString first(QLatin1String("%1 %2")); + QString last(QLatin1String(" HTTP/%3.%4\r\n%5\r\n")); + return first.arg(d->m).arg(d->p) + + last.arg(d->majVer).arg(d->minVer).arg(QHttpHeader::toString()); +} + + +/**************************************************** + * + * QHttp + * + ****************************************************/ +/*! + \class QHttp + \reentrant + + \brief The QHttp class provides an implementation of the HTTP protocol. + + \ingroup io + \inmodule QtNetwork + \mainclass + + This class provides a direct interface to HTTP that allows you to + have more control over the requests and that allows you to access + the response header fields. However, for new applications, it is + recommended to use QNetworkAccessManager and QNetworkReply, as + those classes possess a simpler, yet more powerful API. + + The class works asynchronously, so there are no blocking + functions. If an operation cannot be executed immediately, the + function will still return straight away and the operation will be + scheduled for later execution. The results of scheduled operations + are reported via signals. This approach depends on the event loop + being in operation. + + The operations that can be scheduled (they are called "requests" + in the rest of the documentation) are the following: setHost(), + get(), post(), head() and request(). + + All of these requests return a unique identifier that allows you + to keep track of the request that is currently executed. When the + execution of a request starts, the requestStarted() signal with + the identifier is emitted and when the request is finished, the + requestFinished() signal is emitted with the identifier and a bool + that indicates if the request finished with an error. + + To make an HTTP request you must set up suitable HTTP headers. The + following example demonstrates, how to request the main HTML page + from the Trolltech home page (i.e., the URL + \c http://qtsoftware.com/index.html): + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 2 + + For the common HTTP requests \c GET, \c POST and \c HEAD, QHttp + provides the convenience functions get(), post() and head(). They + already use a reasonable header and if you don't have to set + special header fields, they are easier to use. The above example + can also be written as: + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 3 + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 4 + + The dataSendProgress() and dataReadProgress() signals in the above + example are useful if you want to show a \link QProgressBar + progress bar\endlink to inform the user about the progress of the + download. The second argument is the total size of data. In + certain cases it is not possible to know the total amount in + advance, in which case the second argument is 0. (If you connect + to a QProgressBar a total of 0 results in a busy indicator.) + + When the response header is read, it is reported with the + responseHeaderReceived() signal. + + The readyRead() signal tells you that there is data ready to be + read. The amount of data can then be queried with the + bytesAvailable() function and it can be read with the read() + or readAll() functions. + + If an error occurs during the execution of one of the commands in + a sequence of commands, all the pending commands (i.e. scheduled, + but not yet executed commands) are cleared and no signals are + emitted for them. + + For example, if you have the following sequence of requests + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 5 + + and the get() request fails because the host lookup fails, then + the post() request is never executed and the signals would look + like this: + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 6 + + You can then get details about the error with the error() and + errorString() functions. Note that only unexpected behavior, like + network failure is considered as an error. If the server response + contains an error status, like a 404 response, this is reported as + a normal response case. So you should always check the \link + QHttpResponseHeader::statusCode() status code \endlink of the + response header. + + The functions currentId() and currentRequest() provide more + information about the currently executing request. + + The functions hasPendingRequests() and clearPendingRequests() + allow you to query and clear the list of pending requests. + + \sa QFtp, QNetworkAccessManager, QNetworkRequest, QNetworkReply, + {HTTP Example}, {Torrent Example} +*/ + +/*! + Constructs a QHttp object. The \a parent parameter is passed on + to the QObject constructor. +*/ +QHttp::QHttp(QObject *parent) + : QObject(*new QHttpPrivate, parent) +{ + Q_D(QHttp); + d->init(); +} + +/*! + Constructs a QHttp object. Subsequent requests are done by + connecting to the server \a hostName on port \a port. + + The \a parent parameter is passed on to the QObject constructor. + + \sa setHost() +*/ +QHttp::QHttp(const QString &hostName, quint16 port, QObject *parent) + : QObject(*new QHttpPrivate, parent) +{ + Q_D(QHttp); + d->init(); + + d->hostName = hostName; + d->port = port; +} + +/*! + Constructs a QHttp object. Subsequent requests are done by + connecting to the server \a hostName on port \a port using the + connection mode \a mode. + + If port is 0, it will use the default port for the \a mode used + (80 for Http and 443 for Https). + + The \a parent parameter is passed on to the QObject constructor. + + \sa setHost() +*/ +QHttp::QHttp(const QString &hostName, ConnectionMode mode, quint16 port, QObject *parent) + : QObject(*new QHttpPrivate, parent) +{ + Q_D(QHttp); + d->init(); + + d->hostName = hostName; + if (port == 0) + port = (mode == ConnectionModeHttp) ? 80 : 443; + d->port = port; + d->mode = mode; +} + +void QHttpPrivate::init() +{ + Q_Q(QHttp); + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Unknown error")); + QMetaObject::invokeMethod(q, "_q_slotDoFinished", Qt::QueuedConnection); + post100ContinueTimer.setSingleShot(true); + QObject::connect(&post100ContinueTimer, SIGNAL(timeout()), q, SLOT(_q_continuePost())); +} + +/*! + Destroys the QHttp object. If there is an open connection, it is + closed. +*/ +QHttp::~QHttp() +{ + abort(); +} + +/*! + \enum QHttp::ConnectionMode + \since 4.3 + + This enum is used to specify the mode of connection to use: + + \value ConnectionModeHttp The connection is a regular Http connection to the server + \value ConnectionModeHttps The Https protocol is used and the connection is encrypted using SSL. + + When using the Https mode, care should be taken to connect to the sslErrors signal, and + handle possible Ssl errors. + + \sa QSslSocket +*/ + +/*! + \enum QHttp::State + + This enum is used to specify the state the client is in: + + \value Unconnected There is no connection to the host. + \value HostLookup A host name lookup is in progress. + \value Connecting An attempt to connect to the host is in progress. + \value Sending The client is sending its request to the server. + \value Reading The client's request has been sent and the client + is reading the server's response. + \value Connected The connection to the host is open, but the client is + neither sending a request, nor waiting for a response. + \value Closing The connection is closing down, but is not yet + closed. (The state will be \c Unconnected when the connection is + closed.) + + \sa stateChanged() state() +*/ + +/*! \enum QHttp::Error + + This enum identifies the error that occurred. + + \value NoError No error occurred. + \value HostNotFound The host name lookup failed. + \value ConnectionRefused The server refused the connection. + \value UnexpectedClose The server closed the connection unexpectedly. + \value InvalidResponseHeader The server sent an invalid response header. + \value WrongContentLength The client could not read the content correctly + because an error with respect to the content length occurred. + \value Aborted The request was aborted with abort(). + \value ProxyAuthenticationRequiredError QHttp is using a proxy, and the + proxy server requires authentication to establish a connection. + \value AuthenticationRequiredError The web server requires authentication + to complete the request. + \value UnknownError An error other than those specified above + occurred. + + \sa error() +*/ + +/*! + \fn void QHttp::stateChanged(int state) + + This signal is emitted when the state of the QHttp object changes. + The argument \a state is the new state of the connection; it is + one of the \l State values. + + This usually happens when a request is started, but it can also + happen when the server closes the connection or when a call to + close() succeeded. + + \sa get() post() head() request() close() state() State +*/ + +/*! + \fn void QHttp::responseHeaderReceived(const QHttpResponseHeader &resp); + + This signal is emitted when the HTTP header of a server response + is available. The header is passed in \a resp. + + \sa get() post() head() request() readyRead() +*/ + +/*! + \fn void QHttp::readyRead(const QHttpResponseHeader &resp) + + This signal is emitted when there is new response data to read. + + If you specified a device in the request where the data should be + written to, then this signal is \e not emitted; instead the data + is written directly to the device. + + The response header is passed in \a resp. + + You can read the data with the readAll() or read() functions + + This signal is useful if you want to process the data in chunks as + soon as it becomes available. If you are only interested in the + complete data, just connect to the requestFinished() signal and + read the data then instead. + + \sa get() post() request() readAll() read() bytesAvailable() +*/ + +/*! + \fn void QHttp::dataSendProgress(int done, int total) + + This signal is emitted when this object sends data to a HTTP + server to inform it about the progress of the upload. + + \a done is the amount of data that has already arrived and \a + total is the total amount of data. It is possible that the total + amount of data that should be transferred cannot be determined, in + which case \a total is 0.(If you connect to a QProgressBar, the + progress bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa dataReadProgress(), post(), request(), QProgressBar +*/ + +/*! + \fn void QHttp::dataReadProgress(int done, int total) + + This signal is emitted when this object reads data from a HTTP + server to indicate the current progress of the download. + + \a done is the amount of data that has already arrived and \a + total is the total amount of data. It is possible that the total + amount of data that should be transferred cannot be determined, in + which case \a total is 0.(If you connect to a QProgressBar, the + progress bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa dataSendProgress() get() post() request() QProgressBar +*/ + +/*! + \fn void QHttp::requestStarted(int id) + + This signal is emitted when processing the request identified by + \a id starts. + + \sa requestFinished() done() +*/ + +/*! + \fn void QHttp::requestFinished(int id, bool error) + + This signal is emitted when processing the request identified by + \a id has finished. \a error is true if an error occurred during + the processing; otherwise \a error is false. + + \sa requestStarted() done() error() errorString() +*/ + +/*! + \fn void QHttp::done(bool error) + + This signal is emitted when the last pending request has finished; + (it is emitted after the last request's requestFinished() signal). + \a error is true if an error occurred during the processing; + otherwise \a error is false. + + \sa requestFinished() error() errorString() +*/ + +#ifndef QT_NO_NETWORKPROXY + +/*! + \fn void QHttp::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + \since 4.3 + + This signal can be emitted when a \a proxy that requires + authentication is used. The \a authenticator object can then be + filled in with the required details to allow authentication and + continue the connection. + + \note It is not possible to use a QueuedConnection to connect to + this signal, as the connection will fail if the authenticator has + not been filled in with new information when the signal returns. + + \sa QAuthenticator, QNetworkProxy +*/ + +#endif + +/*! + \fn void QHttp::authenticationRequired(const QString &hostname, quint16 port, QAuthenticator *authenticator) + \since 4.3 + + This signal can be emitted when a web server on a given \a hostname and \a + port requires authentication. The \a authenticator object can then be + filled in with the required details to allow authentication and continue + the connection. + + \note It is not possible to use a QueuedConnection to connect to + this signal, as the connection will fail if the authenticator has + not been filled in with new information when the signal returns. + + \sa QAuthenticator, QNetworkProxy +*/ + +/*! + \fn void QHttp::sslErrors(const QList<QSslError> &errors) + \since 4.3 + + Forwards the sslErrors signal from the QSslSocket used in QHttp. \a errors + is the list of errors that occurred during the SSL handshake. Unless you + call ignoreSslErrors() from within a slot connected to this signal when an + error occurs, QHttp will tear down the connection immediately after + emitting the signal. + + \sa QSslSocket QSslSocket::ignoreSslErrors() +*/ + +/*! + Aborts the current request and deletes all scheduled requests. + + For the current request, the requestFinished() signal with the \c + error argument \c true is emitted. For all other requests that are + affected by the abort(), no signals are emitted. + + Since this slot also deletes the scheduled requests, there are no + requests left and the done() signal is emitted (with the \c error + argument \c true). + + \sa clearPendingRequests() +*/ +void QHttp::abort() +{ + Q_D(QHttp); + if (d->pending.isEmpty()) + return; + + d->finishedWithError(tr("Request aborted"), Aborted); + clearPendingRequests(); + if (d->socket) + d->socket->abort(); + d->closeConn(); +} + +/*! + Returns the number of bytes that can be read from the response + content at the moment. + + \sa get() post() request() readyRead() read() readAll() +*/ +qint64 QHttp::bytesAvailable() const +{ + Q_D(const QHttp); +#if defined(QHTTP_DEBUG) + qDebug("QHttp::bytesAvailable(): %d bytes", (int)d->rba.size()); +#endif + return qint64(d->rba.size()); +} + +/*! \fn qint64 QHttp::readBlock(char *data, quint64 maxlen) + + Use read() instead. +*/ + +/*! + Reads \a maxlen bytes from the response content into \a data and + returns the number of bytes read. Returns -1 if an error occurred. + + \sa get() post() request() readyRead() bytesAvailable() readAll() +*/ +qint64 QHttp::read(char *data, qint64 maxlen) +{ + Q_D(QHttp); + if (data == 0 && maxlen != 0) { + qWarning("QHttp::read: Null pointer error"); + return -1; + } + if (maxlen >= d->rba.size()) + maxlen = d->rba.size(); + int readSoFar = 0; + while (!d->rba.isEmpty() && readSoFar < maxlen) { + int nextBlockSize = d->rba.nextDataBlockSize(); + int bytesToRead = qMin<qint64>(maxlen - readSoFar, nextBlockSize); + memcpy(data + readSoFar, d->rba.readPointer(), bytesToRead); + d->rba.free(bytesToRead); + readSoFar += bytesToRead; + } + + d->bytesDone += maxlen; +#if defined(QHTTP_DEBUG) + qDebug("QHttp::read(): read %lld bytes (%lld bytes done)", maxlen, d->bytesDone); +#endif + return maxlen; +} + +/*! + Reads all the bytes from the response content and returns them. + + \sa get() post() request() readyRead() bytesAvailable() read() +*/ +QByteArray QHttp::readAll() +{ + qint64 avail = bytesAvailable(); + QByteArray tmp; + tmp.resize(int(avail)); + qint64 got = read(tmp.data(), int(avail)); + tmp.resize(got); + return tmp; +} + +/*! + Returns the identifier of the HTTP request being executed or 0 if + there is no request being executed (i.e. they've all finished). + + \sa currentRequest() +*/ +int QHttp::currentId() const +{ + Q_D(const QHttp); + if (d->pending.isEmpty()) + return 0; + return d->pending.first()->id; +} + +/*! + Returns the request header of the HTTP request being executed. If + the request is one issued by setHost() or close(), it + returns an invalid request header, i.e. + QHttpRequestHeader::isValid() returns false. + + \sa currentId() +*/ +QHttpRequestHeader QHttp::currentRequest() const +{ + Q_D(const QHttp); + if (!d->pending.isEmpty()) { + QHttpRequest *r = d->pending.first(); + if (r->hasRequestHeader()) + return r->requestHeader(); + } + return QHttpRequestHeader(); +} + +/*! + Returns the received response header of the most recently finished HTTP + request. If no response has yet been received + QHttpResponseHeader::isValid() will return false. + + \sa currentRequest() +*/ +QHttpResponseHeader QHttp::lastResponse() const +{ + Q_D(const QHttp); + return d->response; +} + +/*! + Returns the QIODevice pointer that is used as the data source of the HTTP + request being executed. If there is no current request or if the request + does not use an IO device as the data source, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the requestFinished() signal. + + \sa currentDestinationDevice() post() request() +*/ +QIODevice *QHttp::currentSourceDevice() const +{ + Q_D(const QHttp); + if (d->pending.isEmpty()) + return 0; + return d->pending.first()->sourceDevice(); +} + +/*! + Returns the QIODevice pointer that is used as to store the data of the HTTP + request being executed. If there is no current request or if the request + does not store the data to an IO device, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the requestFinished() signal. + + \sa currentSourceDevice() get() post() request() +*/ +QIODevice *QHttp::currentDestinationDevice() const +{ + Q_D(const QHttp); + if (d->pending.isEmpty()) + return 0; + return d->pending.first()->destinationDevice(); +} + +/*! + Returns true if there are any requests scheduled that have not yet + been executed; otherwise returns false. + + The request that is being executed is \e not considered as a + scheduled request. + + \sa clearPendingRequests() currentId() currentRequest() +*/ +bool QHttp::hasPendingRequests() const +{ + Q_D(const QHttp); + return d->pending.count() > 1; +} + +/*! + Deletes all pending requests from the list of scheduled requests. + This does not affect the request that is being executed. If + you want to stop this as well, use abort(). + + \sa hasPendingRequests() abort() +*/ +void QHttp::clearPendingRequests() +{ + Q_D(QHttp); + // delete all entires except the first one + while (d->pending.count() > 1) + delete d->pending.takeLast(); +} + +/*! + Sets the HTTP server that is used for requests to \a hostName on + port \a port. + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa get() post() head() request() requestStarted() requestFinished() done() +*/ +int QHttp::setHost(const QString &hostName, quint16 port) +{ + Q_D(QHttp); + return d->addRequest(new QHttpSetHostRequest(hostName, port, ConnectionModeHttp)); +} + +/*! + Sets the HTTP server that is used for requests to \a hostName on + port \a port using the connection mode \a mode. + + If port is 0, it will use the default port for the \a mode used + (80 for Http and 443 fopr Https). + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa get() post() head() request() requestStarted() requestFinished() done() +*/ +int QHttp::setHost(const QString &hostName, ConnectionMode mode, quint16 port) +{ +#ifdef QT_NO_OPENSSL + if (mode == ConnectionModeHttps) + qWarning("QHttp::setHost: HTTPS connection requested but SSL support not compiled in"); +#endif + Q_D(QHttp); + if (port == 0) + port = (mode == ConnectionModeHttp) ? 80 : 443; + return d->addRequest(new QHttpSetHostRequest(hostName, port, mode)); +} + +/*! + Replaces the internal QTcpSocket that QHttp uses with \a + socket. This is useful if you want to use your own custom QTcpSocket + subclass instead of the plain QTcpSocket that QHttp uses by default. + QHttp does not take ownership of the socket, and will not delete \a + socket when destroyed. + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + Note: If QHttp is used in a non-GUI thread that runs its own event + loop, you must move \a socket to that thread before calling setSocket(). + + \sa QObject::moveToThread(), {Thread Support in Qt} +*/ +int QHttp::setSocket(QTcpSocket *socket) +{ + Q_D(QHttp); + return d->addRequest(new QHttpSetSocketRequest(socket)); +} + +/*! + This function sets the user name \a userName and password \a + password for web pages that require authentication. + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. +*/ +int QHttp::setUser(const QString &userName, const QString &password) +{ + Q_D(QHttp); + return d->addRequest(new QHttpSetUserRequest(userName, password)); +} + +#ifndef QT_NO_NETWORKPROXY + +/*! + Enables HTTP proxy support, using the proxy server \a host on port \a + port. \a username and \a password can be provided if the proxy server + requires authentication. + + Example: + + \snippet doc/src/snippets/code/src_network_access_qhttp.cpp 7 + + QHttp supports non-transparent web proxy servers only, such as the Squid + Web proxy cache server (from \l http://www.squid.org/). For transparent + proxying, such as SOCKS5, use QNetworkProxy instead. + + \sa QFtp::setProxy() +*/ +int QHttp::setProxy(const QString &host, int port, + const QString &username, const QString &password) +{ + Q_D(QHttp); + QNetworkProxy proxy(QNetworkProxy::HttpProxy, host, port, username, password); + return d->addRequest(new QHttpSetProxyRequest(proxy)); +} + +/*! + \overload + + Enables HTTP proxy support using the proxy settings from \a + proxy. If \a proxy is a transparent proxy, QHttp will call + QAbstractSocket::setProxy() on the underlying socket. If the type + is QNetworkProxy::HttpCachingProxy, QHttp will behave like the + previous function. + + Note: for compatibility with Qt 4.3, if the proxy type is + QNetworkProxy::HttpProxy and the request type is unencrypted (that + is, ConnectionModeHttp), QHttp will treat the proxy as a caching + proxy. +*/ +int QHttp::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QHttp); + return d->addRequest(new QHttpSetProxyRequest(proxy)); +} + +#endif + +/*! + Sends a get request for \a path to the server set by setHost() or + as specified in the constructor. + + \a path must be a absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html and + must be encoded with either QUrl::toPercentEncoding() or + QUrl::encodedPath(). + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + \section1 Request Processing + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost(), post(), head(), request(), requestStarted(), + requestFinished(), done() +*/ +int QHttp::get(const QString &path, QIODevice *to) +{ + Q_D(QHttp); + QHttpRequestHeader header(QLatin1String("GET"), path); + header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive")); + return d->addRequest(new QHttpPGHRequest(header, (QIODevice *) 0, to)); +} + +/*! + Sends a post request for \a path to the server set by setHost() or + as specified in the constructor. + + \a path must be an absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html and + must be encoded with either QUrl::toPercentEncoding() or + QUrl::encodedPath(). + + The incoming data comes via the \a data IO device. + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() head() request() requestStarted() requestFinished() done() +*/ +int QHttp::post(const QString &path, QIODevice *data, QIODevice *to ) +{ + Q_D(QHttp); + QHttpRequestHeader header(QLatin1String("POST"), path); + header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive")); + return d->addRequest(new QHttpPGHRequest(header, data, to)); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int QHttp::post(const QString &path, const QByteArray &data, QIODevice *to) +{ + Q_D(QHttp); + QHttpRequestHeader header(QLatin1String("POST"), path); + header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive")); + return d->addRequest(new QHttpPGHRequest(header, new QByteArray(data), to)); +} + +/*! + Sends a header request for \a path to the server set by setHost() + or as specified in the constructor. + + \a path must be an absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html. + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() post() request() requestStarted() requestFinished() done() +*/ +int QHttp::head(const QString &path) +{ + Q_D(QHttp); + QHttpRequestHeader header(QLatin1String("HEAD"), path); + header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive")); + return d->addRequest(new QHttpPGHRequest(header, (QIODevice*)0, 0)); +} + +/*! + Sends a request to the server set by setHost() or as specified in + the constructor. Uses the \a header as the HTTP request header. + You are responsible for setting up a header that is appropriate + for your request. + + The incoming data comes via the \a data IO device. + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() post() head() requestStarted() requestFinished() done() +*/ +int QHttp::request(const QHttpRequestHeader &header, QIODevice *data, QIODevice *to) +{ + Q_D(QHttp); + return d->addRequest(new QHttpNormalRequest(header, data, to)); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int QHttp::request(const QHttpRequestHeader &header, const QByteArray &data, QIODevice *to ) +{ + Q_D(QHttp); + return d->addRequest(new QHttpNormalRequest(header, new QByteArray(data), to)); +} + +/*! + Closes the connection; this is useful if you have a keep-alive + connection and want to close it. + + For the requests issued with get(), post() and head(), QHttp sets + the connection to be keep-alive. You can also do this using the + header you pass to the request() function. QHttp only closes the + connection to the HTTP server if the response header requires it + to do so. + + The function does not block; instead, it returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + If you want to close the connection immediately, you have to use + abort() instead. + + \sa stateChanged() abort() requestStarted() requestFinished() done() +*/ +int QHttp::close() +{ + Q_D(QHttp); + return d->addRequest(new QHttpCloseRequest()); +} + +/*! + \obsolete + + Behaves the same as close(). +*/ +int QHttp::closeConnection() +{ + Q_D(QHttp); + return d->addRequest(new QHttpCloseRequest()); +} + +int QHttpPrivate::addRequest(QHttpNormalRequest *req) +{ + QHttpRequestHeader h = req->requestHeader(); + if (h.path().isEmpty()) { + // note: the following qWarning is autotested. If you change it, change the test too. + qWarning("QHttp: empty path requested is invalid -- using '/'"); + h.setRequest(h.method(), QLatin1String("/"), h.majorVersion(), h.minorVersion()); + req->setRequestHeader(h); + } + + // contine below + return addRequest(static_cast<QHttpRequest *>(req)); +} + +int QHttpPrivate::addRequest(QHttpRequest *req) +{ + Q_Q(QHttp); + pending.append(req); + + if (pending.count() == 1) { + // don't emit the requestStarted() signal before the id is returned + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + } + return req->id; +} + +void QHttpPrivate::_q_startNextRequest() +{ + Q_Q(QHttp); + if (pending.isEmpty()) + return; + QHttpRequest *r = pending.first(); + + error = QHttp::NoError; + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Unknown error")); + + if (q->bytesAvailable() != 0) + q->readAll(); // clear the data + emit q->requestStarted(r->id); + r->start(q); +} + +void QHttpPrivate::_q_slotSendRequest() +{ + if (hostName.isNull()) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "No server set to connect to")), + QHttp::UnknownError); + return; + } + + QString connectionHost = hostName; + int connectionPort = port; + bool sslInUse = false; + +#ifndef QT_NO_OPENSSL + QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket); + if (mode == QHttp::ConnectionModeHttps || (sslSocket && sslSocket->isEncrypted())) + sslInUse = true; +#endif + +#ifndef QT_NO_NETWORKPROXY + bool cachingProxyInUse = false; + bool transparentProxyInUse = false; + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() == QNetworkProxy::HttpCachingProxy) { + if (proxy.hostName().isEmpty()) + proxy.setType(QNetworkProxy::NoProxy); + else + cachingProxyInUse = true; + } else if (proxy.type() == QNetworkProxy::HttpProxy) { + // Compatibility behaviour: HttpProxy can be used to mean both + // transparent and caching proxy + if (proxy.hostName().isEmpty()) { + proxy.setType(QNetworkProxy::NoProxy); + } else if (sslInUse) { + // Disallow use of cacheing proxy with HTTPS; instead fall back to + // transparent HTTP CONNECT proxying. + transparentProxyInUse = true; + } else { + proxy.setType(QNetworkProxy::HttpCachingProxy); + cachingProxyInUse = true; + } + } + + // Proxy support. Insert the Proxy-Authorization item into the + // header before it's sent off to the proxy. + if (cachingProxyInUse) { + QUrl proxyUrl; + proxyUrl.setScheme(QLatin1String("http")); + proxyUrl.setHost(hostName); + if (port && port != 80) + proxyUrl.setPort(port); + QString request = QString::fromAscii(proxyUrl.resolved(QUrl::fromEncoded(header.path().toLatin1())).toEncoded()); + + header.setRequest(header.method(), request, header.majorVersion(), header.minorVersion()); + header.setValue(QLatin1String("Proxy-Connection"), QLatin1String("keep-alive")); + + QAuthenticatorPrivate *auth = QAuthenticatorPrivate::getPrivate(proxyAuthenticator); + if (auth && auth->method != QAuthenticatorPrivate::None) { + QByteArray response = auth->calculateResponse(header.method().toLatin1(), header.path().toLatin1()); + header.setValue(QLatin1String("Proxy-Authorization"), QString::fromLatin1(response)); + } + + connectionHost = proxy.hostName(); + connectionPort = proxy.port(); + } + + if (transparentProxyInUse || sslInUse) { + socket->setProxy(proxy); + } +#endif + + // Username support. Insert the user and password into the query + // string. + QAuthenticatorPrivate *auth = QAuthenticatorPrivate::getPrivate(authenticator); + if (auth && auth->method != QAuthenticatorPrivate::None) { + QByteArray response = auth->calculateResponse(header.method().toLatin1(), header.path().toLatin1()); + header.setValue(QLatin1String("Authorization"), QString::fromLatin1(response)); + } + + // Do we need to setup a new connection or can we reuse an + // existing one? + if (socket->peerName() != connectionHost || socket->peerPort() != connectionPort + || socket->state() != QTcpSocket::ConnectedState +#ifndef QT_NO_OPENSSL + || (sslSocket && sslSocket->isEncrypted() != (mode == QHttp::ConnectionModeHttps)) +#endif + ) { + socket->blockSignals(true); + socket->abort(); + socket->blockSignals(false); + + setState(QHttp::Connecting); +#ifndef QT_NO_OPENSSL + if (sslSocket && mode == QHttp::ConnectionModeHttps) { + sslSocket->connectToHostEncrypted(hostName, port); + } else +#endif + { + socket->connectToHost(connectionHost, connectionPort); + } + } else { + _q_slotConnected(); + } + +} + +void QHttpPrivate::finishedWithSuccess() +{ + Q_Q(QHttp); + if (pending.isEmpty()) + return; + QHttpRequest *r = pending.first(); + + // did we recurse? + if (r->finished) + return; + r->finished = true; + hasFinishedWithError = false; + + emit q->requestFinished(r->id, false); + if (hasFinishedWithError) { + // we recursed and changed into an error. The finishedWithError function + // below has emitted the done(bool) signal and cleared the queue by now. + return; + } + + pending.removeFirst(); + delete r; + + if (pending.isEmpty()) { + emit q->done(false); + } else { + _q_startNextRequest(); + } +} + +void QHttpPrivate::finishedWithError(const QString &detail, int errorCode) +{ + Q_Q(QHttp); + if (pending.isEmpty()) + return; + QHttpRequest *r = pending.first(); + hasFinishedWithError = true; + + error = QHttp::Error(errorCode); + errorString = detail; + + // did we recurse? + if (!r->finished) { + r->finished = true; + emit q->requestFinished(r->id, true); + } + + while (!pending.isEmpty()) + delete pending.takeFirst(); + emit q->done(hasFinishedWithError); +} + +void QHttpPrivate::_q_slotClosed() +{ + Q_Q(QHttp); + + if (state == QHttp::Reading) { + if (response.hasKey(QLatin1String("content-length"))) { + // We got Content-Length, so did we get all bytes? + if (bytesDone + q->bytesAvailable() != response.contentLength()) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Wrong content length")), QHttp::WrongContentLength); + } + } + } else if (state == QHttp::Connecting || state == QHttp::Sending) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Server closed connection unexpectedly")), QHttp::UnexpectedClose); + } + + postDevice = 0; + if (state != QHttp::Closing) + setState(QHttp::Closing); + QMetaObject::invokeMethod(q, "_q_slotDoFinished", Qt::QueuedConnection); +} + +void QHttpPrivate::_q_continuePost() +{ + if (pendingPost) { + pendingPost = false; + setState(QHttp::Sending); + _q_slotBytesWritten(0); + } +} + +void QHttpPrivate::_q_slotConnected() +{ + if (state != QHttp::Sending) { + bytesDone = 0; + setState(QHttp::Sending); + } + + QString str = header.toString(); + bytesTotal = str.length(); + socket->write(str.toLatin1(), bytesTotal); +#if defined(QHTTP_DEBUG) + qDebug("QHttp: write request header %p:\n---{\n%s}---", &header, str.toLatin1().constData()); +#endif + + if (postDevice) { + postDevice->seek(0); // reposition the device + bytesTotal += postDevice->size(); + //check for 100-continue + if (header.value(QLatin1String("expect")).contains(QLatin1String("100-continue"), Qt::CaseInsensitive)) { + //create a time out for 2 secs. + pendingPost = true; + post100ContinueTimer.start(2000); + } + } else { + bytesTotal += buffer.size(); + socket->write(buffer, buffer.size()); + } +} + +void QHttpPrivate::_q_slotError(QAbstractSocket::SocketError err) +{ + Q_Q(QHttp); + postDevice = 0; + + if (state == QHttp::Connecting || state == QHttp::Reading || state == QHttp::Sending) { + switch (err) { + case QTcpSocket::ConnectionRefusedError: + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Connection refused (or timed out)")), QHttp::ConnectionRefused); + break; + case QTcpSocket::HostNotFoundError: + finishedWithError(QString::fromLatin1(QT_TRANSLATE_NOOP("QHttp", "Host %1 not found")) + .arg(socket->peerName()), QHttp::HostNotFound); + break; + case QTcpSocket::RemoteHostClosedError: + if (state == QHttp::Sending && reconnectAttempts--) { + setState(QHttp::Closing); + setState(QHttp::Unconnected); + socket->blockSignals(true); + socket->abort(); + socket->blockSignals(false); + QMetaObject::invokeMethod(q, "_q_slotSendRequest", Qt::QueuedConnection); + return; + } + break; +#ifndef QT_NO_NETWORKPROXY + case QTcpSocket::ProxyAuthenticationRequiredError: + finishedWithError(socket->errorString(), QHttp::ProxyAuthenticationRequiredError); + break; +#endif + default: + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTP request failed")), QHttp::UnknownError); + break; + } + } + + closeConn(); +} + +void QHttpPrivate::_q_slotBytesWritten(qint64 written) +{ + Q_Q(QHttp); + bytesDone += written; + emit q->dataSendProgress(bytesDone, bytesTotal); + + if (pendingPost) + return; + + if (!postDevice) + return; + + if (socket->bytesToWrite() == 0) { + int max = qMin<qint64>(4096, postDevice->size() - postDevice->pos()); + QByteArray arr; + arr.resize(max); + + int n = postDevice->read(arr.data(), max); + if (n < 0) { + qWarning("Could not read enough bytes from the device"); + closeConn(); + return; + } + if (postDevice->atEnd()) { + postDevice = 0; + } + + socket->write(arr, n); + } +} + +void QHttpPrivate::_q_slotReadyRead() +{ + Q_Q(QHttp); + QHttp::State oldState = state; + if (state != QHttp::Reading) { + setState(QHttp::Reading); + readHeader = true; + headerStr = QLatin1String(""); + bytesDone = 0; + chunkedSize = -1; + repost = false; + } + + while (readHeader) { + bool end = false; + QString tmp; + while (!end && socket->canReadLine()) { + tmp = QString::fromAscii(socket->readLine()); + if (tmp == QLatin1String("\r\n") || tmp == QLatin1String("\n") || tmp.isEmpty()) + end = true; + else + headerStr += tmp; + } + + if (!end) + return; + + response = QHttpResponseHeader(headerStr); + headerStr = QLatin1String(""); +#if defined(QHTTP_DEBUG) + qDebug("QHttp: read response header:\n---{\n%s}---", response.toString().toLatin1().constData()); +#endif + // Check header + if (!response.isValid()) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Invalid HTTP response header")), + QHttp::InvalidResponseHeader); + closeConn(); + return; + } + + int statusCode = response.statusCode(); + if (statusCode == 401 || statusCode == 407) { // (Proxy) Authentication required + QAuthenticator *auth = +#ifndef QT_NO_NETWORKPROXY + statusCode == 407 + ? &proxyAuthenticator : +#endif + &authenticator; + if (auth->isNull()) + auth->detach(); + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth); + priv->parseHttpResponse(response, (statusCode == 407)); + if (priv->phase == QAuthenticatorPrivate::Done) { + socket->blockSignals(true); +#ifndef QT_NO_NETWORKPROXY + if (statusCode == 407) + emit q->proxyAuthenticationRequired(proxy, auth); + else +#endif + emit q->authenticationRequired(hostName, port, auth); + socket->blockSignals(false); + } else if (priv->phase == QAuthenticatorPrivate::Invalid) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Unknown authentication method")), + QHttp::AuthenticationRequiredError); + closeConn(); + return; + } + + // priv->phase will get reset to QAuthenticatorPrivate::Start if the authenticator got modified in the signal above. + if (priv->phase == QAuthenticatorPrivate::Done) { +#ifndef QT_NO_NETWORKPROXY + if (statusCode == 407) + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Proxy authentication required")), + QHttp::ProxyAuthenticationRequiredError); + else +#endif + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Authentication required")), + QHttp::AuthenticationRequiredError); + closeConn(); + return; + } else { + // close the connection if it isn't already and reconnect using the chosen authentication method + bool willClose = (response.value(QLatin1String("proxy-connection")).toLower() == QLatin1String("close")) + || (response.value(QLatin1String("connection")).toLower() == QLatin1String("close")); + if (willClose) { + if (socket) { + setState(QHttp::Closing); + socket->blockSignals(true); + socket->close(); + socket->blockSignals(false); + socket->readAll(); + } + _q_slotSendRequest(); + return; + } else { + repost = true; + } + } + } else { + buffer.clear(); + } + + if (response.statusCode() == 100 && pendingPost) { + // if we have pending POST, start sending data otherwise ignore + post100ContinueTimer.stop(); + QMetaObject::invokeMethod(q, "_q_continuePost", Qt::QueuedConnection); + return; + } + + // The 100-continue header is ignored (in case of no 'expect:100-continue' header), + // because when using the POST method, we send both the request header and data in + // one chunk. + if (response.statusCode() != 100) { + post100ContinueTimer.stop(); + pendingPost = false; + readHeader = false; + if (response.hasKey(QLatin1String("transfer-encoding")) && + response.value(QLatin1String("transfer-encoding")).toLower().contains(QLatin1String("chunked"))) + chunkedSize = 0; + + if (!repost) + emit q->responseHeaderReceived(response); + if (state == QHttp::Unconnected || state == QHttp::Closing) + return; + } else { + // Restore the state, the next incoming data will be treated as if + // we never say the 100 response. + state = oldState; + } + } + + bool everythingRead = false; + + if (q->currentRequest().method() == QLatin1String("HEAD") || + response.statusCode() == 304 || response.statusCode() == 204 || + response.statusCode() == 205) { + // HEAD requests have only headers as replies + // These status codes never have a body: + // 304 Not Modified + // 204 No Content + // 205 Reset Content + everythingRead = true; + } else { + qint64 n = socket->bytesAvailable(); + QByteArray *arr = 0; + if (chunkedSize != -1) { + // transfer-encoding is chunked + for (;;) { + // get chunk size + if (chunkedSize == 0) { + if (!socket->canReadLine()) + break; + QString sizeString = QString::fromAscii(socket->readLine()); + int tPos = sizeString.indexOf(QLatin1Char(';')); + if (tPos != -1) + sizeString.truncate(tPos); + bool ok; + chunkedSize = sizeString.toInt(&ok, 16); + if (!ok) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Invalid HTTP chunked body")), + QHttp::WrongContentLength); + closeConn(); + delete arr; + return; + } + if (chunkedSize == 0) // last-chunk + chunkedSize = -2; + } + + // read trailer + while (chunkedSize == -2 && socket->canReadLine()) { + QString read = QString::fromAscii(socket->readLine()); + if (read == QLatin1String("\r\n") || read == QLatin1String("\n")) + chunkedSize = -1; + } + if (chunkedSize == -1) { + everythingRead = true; + break; + } + + // make sure that you can read the terminating CRLF, + // otherwise wait until next time... + n = socket->bytesAvailable(); + if (n == 0) + break; + if (n == chunkedSize || n == chunkedSize+1) { + n = chunkedSize - 1; + if (n == 0) + break; + } + + // read data + qint64 toRead = chunkedSize < 0 ? n : qMin(n, chunkedSize); + if (!arr) + arr = new QByteArray; + uint oldArrSize = arr->size(); + arr->resize(oldArrSize + toRead); + qint64 read = socket->read(arr->data()+oldArrSize, toRead); + arr->resize(oldArrSize + read); + + chunkedSize -= read; + + if (chunkedSize == 0 && n - read >= 2) { + // read terminating CRLF + char tmp[2]; + socket->read(tmp, 2); + if (tmp[0] != '\r' || tmp[1] != '\n') { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Invalid HTTP chunked body")), + QHttp::WrongContentLength); + closeConn(); + delete arr; + return; + } + } + } + } else if (response.hasContentLength()) { + if (repost && (n < response.contentLength())) { + // wait for the content to be available fully + // if repost is required, the content is ignored + return; + } + n = qMin(qint64(response.contentLength() - bytesDone), n); + if (n > 0) { + arr = new QByteArray; + arr->resize(n); + qint64 read = socket->read(arr->data(), n); + arr->resize(read); + } + if (bytesDone + q->bytesAvailable() + n == response.contentLength()) + everythingRead = true; + } else if (n > 0) { + // workaround for VC++ bug + QByteArray temp = socket->readAll(); + arr = new QByteArray(temp); + } + + if (arr && !repost) { + n = arr->size(); + if (toDevice) { + qint64 bytesWritten; + bytesWritten = toDevice->write(*arr, n); + delete arr; + arr = 0; + // if writing to the device does not succeed, quit with error + if (bytesWritten == -1 || bytesWritten < n) { + finishedWithError(QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Error writing response to device")), QHttp::UnknownError); + } else { + bytesDone += bytesWritten; +#if defined(QHTTP_DEBUG) + qDebug("QHttp::_q_slotReadyRead(): read %lld bytes (%lld bytes done)", n, bytesDone); +#endif + } + if (response.hasContentLength()) + emit q->dataReadProgress(bytesDone, response.contentLength()); + else + emit q->dataReadProgress(bytesDone, 0); + } else { + char *ptr = rba.reserve(arr->size()); + memcpy(ptr, arr->data(), arr->size()); + delete arr; + arr = 0; +#if defined(QHTTP_DEBUG) + qDebug("QHttp::_q_slotReadyRead(): read %lld bytes (%lld bytes done)", n, bytesDone + q->bytesAvailable()); +#endif + if (response.hasContentLength()) + emit q->dataReadProgress(bytesDone + q->bytesAvailable(), response.contentLength()); + else + emit q->dataReadProgress(bytesDone + q->bytesAvailable(), 0); + emit q->readyRead(response); + } + } + + delete arr; + } + + if (everythingRead) { + if (repost) { + _q_slotSendRequest(); + return; + } + // Handle "Connection: close" + if (response.value(QLatin1String("connection")).toLower() == QLatin1String("close")) { + closeConn(); + } else { + setState(QHttp::Connected); + // Start a timer, so that we emit the keep alive signal + // "after" this method returned. + QMetaObject::invokeMethod(q, "_q_slotDoFinished", Qt::QueuedConnection); + } + } +} + +void QHttpPrivate::_q_slotDoFinished() +{ + if (state == QHttp::Connected) { + finishedWithSuccess(); + } else if (state != QHttp::Unconnected) { + setState(QHttp::Unconnected); + finishedWithSuccess(); + } +} + + +/*! + Returns the current state of the object. When the state changes, + the stateChanged() signal is emitted. + + \sa State stateChanged() +*/ +QHttp::State QHttp::state() const +{ + Q_D(const QHttp); + return d->state; +} + +/*! + Returns the last error that occurred. This is useful to find out + what happened when receiving a requestFinished() or a done() + signal with the \c error argument \c true. + + If you start a new request, the error status is reset to \c NoError. +*/ +QHttp::Error QHttp::error() const +{ + Q_D(const QHttp); + return d->error; +} + +/*! + Returns a human-readable description of the last error that + occurred. This is useful to present a error message to the user + when receiving a requestFinished() or a done() signal with the \c + error argument \c true. +*/ +QString QHttp::errorString() const +{ + Q_D(const QHttp); + return d->errorString; +} + +void QHttpPrivate::setState(int s) +{ + Q_Q(QHttp); +#if defined(QHTTP_DEBUG) + qDebug("QHttp state changed %d -> %d", state, s); +#endif + state = QHttp::State(s); + emit q->stateChanged(s); +} + +void QHttpPrivate::closeConn() +{ + Q_Q(QHttp); + // If no connection is open -> ignore + if (state == QHttp::Closing || state == QHttp::Unconnected) + return; + + postDevice = 0; + setState(QHttp::Closing); + + // Already closed ? + if (!socket || !socket->isOpen()) { + QMetaObject::invokeMethod(q, "_q_slotDoFinished", Qt::QueuedConnection); + } else { + // Close now. + socket->close(); + } +} + +void QHttpPrivate::setSock(QTcpSocket *sock) +{ + Q_Q(const QHttp); + + // disconnect all existing signals + if (socket) + socket->disconnect(); + if (deleteSocket) + delete socket; + + // use the new QTcpSocket socket, or create one if socket is 0. + deleteSocket = (sock == 0); + socket = sock; + if (!socket) { +#ifndef QT_NO_OPENSSL + if (QSslSocket::supportsSsl()) + socket = new QSslSocket(); + else +#endif + socket = new QTcpSocket(); + } + + // connect all signals + QObject::connect(socket, SIGNAL(connected()), q, SLOT(_q_slotConnected())); + QObject::connect(socket, SIGNAL(disconnected()), q, SLOT(_q_slotClosed())); + QObject::connect(socket, SIGNAL(readyRead()), q, SLOT(_q_slotReadyRead())); + QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), q, SLOT(_q_slotError(QAbstractSocket::SocketError))); + QObject::connect(socket, SIGNAL(bytesWritten(qint64)), + q, SLOT(_q_slotBytesWritten(qint64))); +#ifndef QT_NO_NETWORKPROXY + QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), + q, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); +#endif + +#ifndef QT_NO_OPENSSL + if (qobject_cast<QSslSocket *>(socket)) { + QObject::connect(socket, SIGNAL(sslErrors(const QList<QSslError> &)), + q, SIGNAL(sslErrors(const QList<QSslError> &))); + } +#endif +} + +/*! + Tells the QSslSocket used for the Http connection to ignore the errors + reported in the sslErrors() signal. + + Note that this function must be called from within a slot connected to the + sslErrors() signal to have any effect. + + \sa QSslSocket QSslSocket::sslErrors() +*/ +#ifndef QT_NO_OPENSSL +void QHttp::ignoreSslErrors() +{ + Q_D(QHttp); + QSslSocket *sslSocket = qobject_cast<QSslSocket *>(d->socket); + if (sslSocket) + sslSocket->ignoreSslErrors(); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qhttp.cpp" + +#endif diff --git a/src/network/access/qhttp.h b/src/network/access/qhttp.h new file mode 100644 index 0000000..771176a --- /dev/null +++ b/src/network/access/qhttp.h @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTP_H +#define QHTTP_H + +#include <QtCore/qobject.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qpair.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_HTTP + +class QTcpSocket; +class QTimerEvent; +class QIODevice; +class QAuthenticator; +class QNetworkProxy; +class QSslError; + +class QHttpPrivate; + +class QHttpHeaderPrivate; +class Q_NETWORK_EXPORT QHttpHeader +{ +public: + QHttpHeader(); + QHttpHeader(const QHttpHeader &header); + QHttpHeader(const QString &str); + virtual ~QHttpHeader(); + + QHttpHeader &operator=(const QHttpHeader &h); + + void setValue(const QString &key, const QString &value); + void setValues(const QList<QPair<QString, QString> > &values); + void addValue(const QString &key, const QString &value); + QList<QPair<QString, QString> > values() const; + bool hasKey(const QString &key) const; + QStringList keys() const; + QString value(const QString &key) const; + QStringList allValues(const QString &key) const; + void removeValue(const QString &key); + void removeAllValues(const QString &key); + + // ### Qt 5: change to qint64 + bool hasContentLength() const; + uint contentLength() const; + void setContentLength(int len); + + bool hasContentType() const; + QString contentType() const; + void setContentType(const QString &type); + + virtual QString toString() const; + bool isValid() const; + + virtual int majorVersion() const = 0; + virtual int minorVersion() const = 0; + +protected: + virtual bool parseLine(const QString &line, int number); + bool parse(const QString &str); + void setValid(bool); + + QHttpHeader(QHttpHeaderPrivate &dd, const QString &str = QString()); + QHttpHeader(QHttpHeaderPrivate &dd, const QHttpHeader &header); + QHttpHeaderPrivate *d_ptr; + +private: + Q_DECLARE_PRIVATE(QHttpHeader) +}; + +class QHttpResponseHeaderPrivate; +class Q_NETWORK_EXPORT QHttpResponseHeader : public QHttpHeader +{ +public: + QHttpResponseHeader(); + QHttpResponseHeader(const QHttpResponseHeader &header); + QHttpResponseHeader(const QString &str); + QHttpResponseHeader(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); + QHttpResponseHeader &operator=(const QHttpResponseHeader &header); + + void setStatusLine(int code, const QString &text = QString(), int majorVer = 1, int minorVer = 1); + + int statusCode() const; + QString reasonPhrase() const; + + int majorVersion() const; + int minorVersion() const; + + QString toString() const; + +protected: + bool parseLine(const QString &line, int number); + +private: + Q_DECLARE_PRIVATE(QHttpResponseHeader) + friend class QHttpPrivate; +}; + +class QHttpRequestHeaderPrivate; +class Q_NETWORK_EXPORT QHttpRequestHeader : public QHttpHeader +{ +public: + QHttpRequestHeader(); + QHttpRequestHeader(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); + QHttpRequestHeader(const QHttpRequestHeader &header); + QHttpRequestHeader(const QString &str); + QHttpRequestHeader &operator=(const QHttpRequestHeader &header); + + void setRequest(const QString &method, const QString &path, int majorVer = 1, int minorVer = 1); + + QString method() const; + QString path() const; + + int majorVersion() const; + int minorVersion() const; + + QString toString() const; + +protected: + bool parseLine(const QString &line, int number); + +private: + Q_DECLARE_PRIVATE(QHttpRequestHeader) +}; + +class Q_NETWORK_EXPORT QHttp : public QObject +{ + Q_OBJECT + +public: + enum ConnectionMode { + ConnectionModeHttp, + ConnectionModeHttps + }; + + explicit QHttp(QObject *parent = 0); + QHttp(const QString &hostname, quint16 port = 80, QObject *parent = 0); + QHttp(const QString &hostname, ConnectionMode mode, quint16 port = 0, QObject *parent = 0); + virtual ~QHttp(); + + enum State { + Unconnected, + HostLookup, + Connecting, + Sending, + Reading, + Connected, + Closing + }; + enum Error { + NoError, + UnknownError, + HostNotFound, + ConnectionRefused, + UnexpectedClose, + InvalidResponseHeader, + WrongContentLength, + Aborted, + AuthenticationRequiredError, + ProxyAuthenticationRequiredError + }; + + int setHost(const QString &hostname, quint16 port = 80); + int setHost(const QString &hostname, ConnectionMode mode, quint16 port = 0); + + int setSocket(QTcpSocket *socket); + int setUser(const QString &username, const QString &password = QString()); + +#ifndef QT_NO_NETWORKPROXY + int setProxy(const QString &host, int port, + const QString &username = QString(), + const QString &password = QString()); + int setProxy(const QNetworkProxy &proxy); +#endif + + int get(const QString &path, QIODevice *to=0); + int post(const QString &path, QIODevice *data, QIODevice *to=0 ); + int post(const QString &path, const QByteArray &data, QIODevice *to=0); + int head(const QString &path); + int request(const QHttpRequestHeader &header, QIODevice *device=0, QIODevice *to=0); + int request(const QHttpRequestHeader &header, const QByteArray &data, QIODevice *to=0); + + int closeConnection(); + int close(); + + qint64 bytesAvailable() const; + qint64 read(char *data, qint64 maxlen); +#ifdef QT3_SUPPORT + inline QT3_SUPPORT qint64 readBlock(char *data, quint64 maxlen) + { return read(data, qint64(maxlen)); } +#endif + QByteArray readAll(); + + int currentId() const; + QIODevice *currentSourceDevice() const; + QIODevice *currentDestinationDevice() const; + QHttpRequestHeader currentRequest() const; + QHttpResponseHeader lastResponse() const; + bool hasPendingRequests() const; + void clearPendingRequests(); + + State state() const; + + Error error() const; + QString errorString() const; + +public Q_SLOTS: + void abort(); + +#ifndef QT_NO_OPENSSL + void ignoreSslErrors(); +#endif + +Q_SIGNALS: + void stateChanged(int); + void responseHeaderReceived(const QHttpResponseHeader &resp); + void readyRead(const QHttpResponseHeader &resp); + + // ### Qt 5: change to qint64 + void dataSendProgress(int, int); + void dataReadProgress(int, int); + + void requestStarted(int); + void requestFinished(int, bool); + void done(bool); + +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *); +#endif + void authenticationRequired(const QString &hostname, quint16 port, QAuthenticator *); + +#ifndef QT_NO_OPENSSL + void sslErrors(const QList<QSslError> &errors); +#endif + +private: + Q_DISABLE_COPY(QHttp) + Q_DECLARE_PRIVATE(QHttp) + + Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest()) + Q_PRIVATE_SLOT(d_func(), void _q_slotReadyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_slotConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_slotError(QAbstractSocket::SocketError)) + Q_PRIVATE_SLOT(d_func(), void _q_slotClosed()) + Q_PRIVATE_SLOT(d_func(), void _q_slotBytesWritten(qint64 numBytes)) + Q_PRIVATE_SLOT(d_func(), void _q_slotDoFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_slotSendRequest()) + Q_PRIVATE_SLOT(d_func(), void _q_continuePost()) + + friend class QHttpNormalRequest; + friend class QHttpSetHostRequest; + friend class QHttpSetSocketRequest; + friend class QHttpSetUserRequest; + friend class QHttpSetProxyRequest; + friend class QHttpCloseRequest; + friend class QHttpPGHRequest; +}; + +#endif // QT_NO_HTTP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QHTTP_H diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp new file mode 100644 index 0000000..edb2988 --- /dev/null +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -0,0 +1,2464 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhttpnetworkconnection_p.h" +#include <private/qnetworkrequest_p.h> +#include <private/qobject_p.h> +#include <private/qauthenticator_p.h> +#include <qnetworkproxy.h> +#include <qauthenticator.h> +#include <qbytearraymatcher.h> +#include <qbuffer.h> +#include <qpair.h> +#include <qhttp.h> +#include <qdebug.h> + +#ifndef QT_NO_HTTP + +#ifndef QT_NO_OPENSSL +# include <QtNetwork/qsslkey.h> +# include <QtNetwork/qsslcipher.h> +# include <QtNetwork/qsslconfiguration.h> +#endif + +#ifndef QT_NO_COMPRESS +# include <zlib.h> +static const unsigned char gz_magic[2] = {0x1f, 0x8b}; // gzip magic header +// gzip flag byte +#define HEAD_CRC 0x02 // bit 1 set: header CRC present +#define EXTRA_FIELD 0x04 // bit 2 set: extra field present +#define ORIG_NAME 0x08 // bit 3 set: original file name present +#define COMMENT 0x10 // bit 4 set: file comment present +#define RESERVED 0xE0 // bits 5..7: reserved +#define CHUNK 16384 +#endif + +QT_BEGIN_NAMESPACE + +class QHttpNetworkHeaderPrivate : public QSharedData +{ +public: + QUrl url; + QList<QPair<QByteArray, QByteArray> > fields; + + QHttpNetworkHeaderPrivate(const QUrl &newUrl = QUrl()); + QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other); + inline qint64 contentLength() const; + inline void setContentLength(qint64 length); + + inline QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const; + inline QList<QByteArray> headerFieldValues(const QByteArray &name) const; + inline void setHeaderField(const QByteArray &name, const QByteArray &data); + bool operator==(const QHttpNetworkHeaderPrivate &other) const; + +}; + +QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QUrl &newUrl) + :url(newUrl) +{ +} + +QHttpNetworkHeaderPrivate::QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other) + :QSharedData(other) +{ + url = other.url; + fields = other.fields; +} + +qint64 QHttpNetworkHeaderPrivate::contentLength() const +{ + bool ok = false; + QByteArray value = headerField("content-length"); + qint64 length = value.toULongLong(&ok); + if (ok) + return length; + return -1; // the header field is not set +} + +void QHttpNetworkHeaderPrivate::setContentLength(qint64 length) +{ + setHeaderField("Content-Length", QByteArray::number(length)); +} + +QByteArray QHttpNetworkHeaderPrivate::headerField(const QByteArray &name, const QByteArray &defaultValue) const +{ + QList<QByteArray> allValues = headerFieldValues(name); + if (allValues.isEmpty()) + return defaultValue; + + QByteArray result; + bool first = true; + foreach (QByteArray value, allValues) { + if (!first) + result += ", "; + first = false; + result += value; + } + return result; +} + +QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray &name) const +{ + QList<QByteArray> result; + QByteArray lowerName = name.toLower(); + QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(), + end = fields.constEnd(); + for ( ; it != end; ++it) + if (lowerName == it->first.toLower()) + result += it->second; + + return result; +} + +void QHttpNetworkHeaderPrivate::setHeaderField(const QByteArray &name, const QByteArray &data) +{ + QByteArray lowerName = name.toLower(); + QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(); + while (it != fields.end()) { + if (lowerName == it->first.toLower()) + it = fields.erase(it); + else + ++it; + } + fields.append(qMakePair(name, data)); +} + +bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const +{ + return (url == other.url); +} + +// QHttpNetworkRequestPrivate +class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate +{ +public: + QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op, + QHttpNetworkRequest::Priority pri, const QUrl &newUrl = QUrl()); + QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other); + ~QHttpNetworkRequestPrivate(); + bool operator==(const QHttpNetworkRequestPrivate &other) const; + QByteArray methodName() const; + QByteArray uri(bool throughProxy) const; + + static QByteArray header(const QHttpNetworkRequest &request, bool throughProxy); + + QHttpNetworkRequest::Operation operation; + QHttpNetworkRequest::Priority priority; + mutable QIODevice *data; + bool autoDecompress; +}; + +QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op, + QHttpNetworkRequest::Priority pri, const QUrl &newUrl) + : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), data(0), + autoDecompress(false) +{ +} + +QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other) + : QHttpNetworkHeaderPrivate(other) +{ + operation = other.operation; + priority = other.priority; + data = other.data; + autoDecompress = other.autoDecompress; +} + +QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate() +{ +} + +bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &other) const +{ + return QHttpNetworkHeaderPrivate::operator==(other) + && (operation == other.operation) + && (data == other.data); +} + +QByteArray QHttpNetworkRequestPrivate::methodName() const +{ + QByteArray ba; + switch (operation) { + case QHttpNetworkRequest::Options: + ba += "OPTIONS"; + break; + case QHttpNetworkRequest::Get: + ba += "GET"; + break; + case QHttpNetworkRequest::Head: + ba += "HEAD"; + break; + case QHttpNetworkRequest::Post: + ba += "POST"; + break; + case QHttpNetworkRequest::Put: + ba += "PUT"; + break; + case QHttpNetworkRequest::Delete: + ba += "DELETE"; + break; + case QHttpNetworkRequest::Trace: + ba += "TRACE"; + break; + case QHttpNetworkRequest::Connect: + ba += "CONNECT"; + break; + default: + break; + } + return ba; +} + +QByteArray QHttpNetworkRequestPrivate::uri(bool throughProxy) const +{ + QUrl::FormattingOptions format(QUrl::RemoveFragment); + + // for POST, query data is send as content + if (operation == QHttpNetworkRequest::Post && !data) + format |= QUrl::RemoveQuery; + // for requests through proxy, the Request-URI contains full url + if (throughProxy) + format |= QUrl::RemoveUserInfo; + else + format |= QUrl::RemoveScheme | QUrl::RemoveAuthority; + QByteArray uri = url.toEncoded(format); + if (uri.isEmpty() || (throughProxy && url.path().isEmpty())) + uri += '/'; + return uri; +} + +QByteArray QHttpNetworkRequestPrivate::header(const QHttpNetworkRequest &request, bool throughProxy) +{ + QByteArray ba = request.d->methodName(); + QByteArray uri = request.d->uri(throughProxy); + ba += " " + uri; + + QString majorVersion = QString::number(request.majorVersion()); + QString minorVersion = QString::number(request.minorVersion()); + ba += " HTTP/" + majorVersion.toLatin1() + "." + minorVersion.toLatin1() + "\r\n"; + + QList<QPair<QByteArray, QByteArray> > fields = request.header(); + QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin(); + for (; it != fields.constEnd(); ++it) + ba += it->first + ": " + it->second + "\r\n"; + if (request.d->operation == QHttpNetworkRequest::Post) { + // add content type, if not set in the request + if (request.headerField("content-type").isEmpty()) + ba += "Content-Type: application/x-www-form-urlencoded\r\n"; + if (!request.d->data && request.d->url.hasQuery()) { + QByteArray query = request.d->url.encodedQuery(); + ba += "Content-Length: "+ QByteArray::number(query.size()) + "\r\n"; + ba += "\r\n"; + ba += query; + } else { + ba += "\r\n"; + } + } else { + ba += "\r\n"; + } + return ba; +} + +class QHttpNetworkReplyPrivate : public QObjectPrivate, public QHttpNetworkHeaderPrivate +{ +public: + QHttpNetworkReplyPrivate(const QUrl &newUrl = QUrl()); + ~QHttpNetworkReplyPrivate(); + qint64 readStatus(QAbstractSocket *socket); + void parseStatus(const QByteArray &status); + qint64 readHeader(QAbstractSocket *socket); + void parseHeader(const QByteArray &header); + qint64 readBody(QAbstractSocket *socket, QIODevice *out); + bool findChallenge(bool forProxy, QByteArray &challenge) const; + QAuthenticatorPrivate::Method authenticationMethod(bool isProxy) const; + void clear(); + + qint64 transferRaw(QIODevice *in, QIODevice *out, qint64 size); + qint64 transferChunked(QIODevice *in, QIODevice *out); + qint64 getChunkSize(QIODevice *in, qint64 *chunkSize); + + qint64 bytesAvailable() const; + bool isChunked(); + bool connectionCloseEnabled(); + bool isGzipped(); +#ifndef QT_NO_COMPRESS + bool gzipCheckHeader(QByteArray &content, int &pos); + int gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated); +#endif + void removeAutoDecompressHeader(); + + enum ReplyState { + NothingDoneState, + ReadingStatusState, + ReadingHeaderState, + ReadingDataState, + AllDoneState + } state; + + QHttpNetworkRequest request; + int statusCode; + int majorVersion; + int minorVersion; + QString errorString; + QString reasonPhrase; + qint64 bodyLength; + qint64 contentRead; + qint64 totalProgress; + QByteArray fragment; + qint64 currentChunkSize; + qint64 currentChunkRead; + QPointer<QHttpNetworkConnection> connection; + bool initInflate; + bool streamEnd; +#ifndef QT_NO_COMPRESS + z_stream inflateStrm; +#endif + bool autoDecompress; + + QByteArray responseData; // uncompressed body + QByteArray compressedData; // compressed body (temporary) + QBuffer requestDataBuffer; + bool requestIsBuffering; + bool requestIsPrepared; +}; + +QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) + : QHttpNetworkHeaderPrivate(newUrl), state(NothingDoneState), statusCode(100), + majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0), + currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false), + autoDecompress(false), requestIsBuffering(false), requestIsPrepared(false) +{ +} + +QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() +{ +} + +void QHttpNetworkReplyPrivate::clear() +{ + state = NothingDoneState; + statusCode = 100; + bodyLength = 0; + contentRead = 0; + totalProgress = 0; + currentChunkSize = 0; + currentChunkRead = 0; + connection = 0; +#ifndef QT_NO_COMPRESS + if (initInflate) + inflateEnd(&inflateStrm); +#endif + initInflate = false; + streamEnd = false; + autoDecompress = false; + fields.clear(); +} + +// QHttpNetworkReplyPrivate +qint64 QHttpNetworkReplyPrivate::bytesAvailable() const +{ + return (state != ReadingDataState ? 0 : fragment.size()); +} + +bool QHttpNetworkReplyPrivate::isGzipped() +{ + QByteArray encoding = headerField("content-encoding"); + return encoding.toLower() == "gzip"; +} + +void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() +{ + // The header "Content-Encoding = gzip" is retained. + // Content-Length is removed since the actual one send by the server is for compressed data + QByteArray name("content-length"); + QByteArray lowerName = name.toLower(); + QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(), + end = fields.end(); + while (it != end) { + if (name == it->first.toLower()) { + fields.erase(it); + break; + } + ++it; + } + +} + +bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const +{ + challenge.clear(); + // find out the type of authentication protocol requested. + QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate"; + // pick the best protocol (has to match parsing in QAuthenticatorPrivate) + QList<QByteArray> challenges = headerFieldValues(header); + for (int i = 0; i<challenges.size(); i++) { + QByteArray line = challenges.at(i); + if (!line.toLower().startsWith("negotiate")) + challenge = line; + } + return !challenge.isEmpty(); +} + +QAuthenticatorPrivate::Method QHttpNetworkReplyPrivate::authenticationMethod(bool isProxy) const +{ + // The logic is same as the one used in void QAuthenticatorPrivate::parseHttpResponse() + QAuthenticatorPrivate::Method method = QAuthenticatorPrivate::None; + QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate"; + QList<QByteArray> challenges = headerFieldValues(header); + for (int i = 0; i<challenges.size(); i++) { + QByteArray line = challenges.at(i).trimmed().toLower(); + if (method < QAuthenticatorPrivate::Basic + && line.startsWith("basic")) { + method = QAuthenticatorPrivate::Basic; + } else if (method < QAuthenticatorPrivate::Ntlm + && line.startsWith("ntlm")) { + method = QAuthenticatorPrivate::Ntlm; + } else if (method < QAuthenticatorPrivate::DigestMd5 + && line.startsWith("digest")) { + method = QAuthenticatorPrivate::DigestMd5; + } + } + return method; +} + +#ifndef QT_NO_COMPRESS +bool QHttpNetworkReplyPrivate::gzipCheckHeader(QByteArray &content, int &pos) +{ + int method = 0; // method byte + int flags = 0; // flags byte + bool ret = false; + + // Assure two bytes in the buffer so we can peek ahead -- handle case + // where first byte of header is at the end of the buffer after the last + // gzip segment + pos = -1; + QByteArray &body = content; + int maxPos = body.size()-1; + if (maxPos < 1) { + return ret; + } + + // Peek ahead to check the gzip magic header + if (body[0] != char(gz_magic[0]) || + body[1] != char(gz_magic[1])) { + return ret; + } + pos += 2; + // Check the rest of the gzip header + if (++pos <= maxPos) + method = body[pos]; + if (pos++ <= maxPos) + flags = body[pos]; + if (method != Z_DEFLATED || (flags & RESERVED) != 0) { + return ret; + } + + // Discard time, xflags and OS code: + pos += 6; + if (pos > maxPos) + return ret; + if ((flags & EXTRA_FIELD) && ((pos+2) <= maxPos)) { // skip the extra field + unsigned len = (unsigned)body[++pos]; + len += ((unsigned)body[++pos])<<8; + pos += len; + if (pos > maxPos) + return ret; + } + if ((flags & ORIG_NAME) != 0) { // skip the original file name + while(++pos <= maxPos && body[pos]) {} + } + if ((flags & COMMENT) != 0) { // skip the .gz file comment + while(++pos <= maxPos && body[pos]) {} + } + if ((flags & HEAD_CRC) != 0) { // skip the header crc + pos += 2; + if (pos > maxPos) + return ret; + } + ret = (pos < maxPos); // return failed, if no more bytes left + return ret; +} + +int QHttpNetworkReplyPrivate::gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated) +{ + int ret = Z_DATA_ERROR; + unsigned have; + unsigned char out[CHUNK]; + int pos = -1; + + if (!initInflate) { + // check the header + if (!gzipCheckHeader(compressed, pos)) + return ret; + // allocate inflate state + inflateStrm.zalloc = Z_NULL; + inflateStrm.zfree = Z_NULL; + inflateStrm.opaque = Z_NULL; + inflateStrm.avail_in = 0; + inflateStrm.next_in = Z_NULL; + ret = inflateInit2(&inflateStrm, -MAX_WBITS); + if (ret != Z_OK) + return ret; + initInflate = true; + streamEnd = false; + } + + //remove the header. + compressed.remove(0, pos+1); + // expand until deflate stream ends + inflateStrm.next_in = (unsigned char *)compressed.data(); + inflateStrm.avail_in = compressed.size(); + do { + inflateStrm.avail_out = sizeof(out); + inflateStrm.next_out = out; + ret = inflate(&inflateStrm, Z_NO_FLUSH); + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + // and fall through + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&inflateStrm); + initInflate = false; + return ret; + } + have = sizeof(out) - inflateStrm.avail_out; + inflated.append(QByteArray((const char *)out, have)); + } while (inflateStrm.avail_out == 0); + // clean up and return + if (ret <= Z_ERRNO || ret == Z_STREAM_END) { + inflateEnd(&inflateStrm); + initInflate = false; + } + streamEnd = (ret == Z_STREAM_END); + return ret; +} +#endif + +qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) +{ + qint64 bytes = 0; + char c; + + while (socket->bytesAvailable()) { + // allow both CRLF & LF (only) line endings + if (socket->peek(&c, 1) == 1 && c == '\n') { + bytes += socket->read(&c, 1); // read the "n" + // remove the CR at the end + if (fragment.endsWith('\r')) { + fragment.truncate(fragment.length()-1); + } + parseStatus(fragment); + state = ReadingHeaderState; + fragment.clear(); // next fragment + break; + } else { + c = 0; + bytes += socket->read(&c, 1); + fragment.append(c); + } + } + return bytes; +} + +void QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status) +{ + const QByteArrayMatcher sp(" "); + int i = sp.indexIn(status); + const QByteArray version = status.mid(0, i); + int j = sp.indexIn(status, i + 1); + const QByteArray code = status.mid(i + 1, j - i - 1); + const QByteArray reason = status.mid(j + 1, status.count() - j); + + const QByteArrayMatcher slash("/"); + int k = slash.indexIn(version); + const QByteArrayMatcher dot("."); + int l = dot.indexIn(version, k); + const QByteArray major = version.mid(k + 1, l - k - 1); + const QByteArray minor = version.mid(l + 1, version.count() - l); + + majorVersion = QString::fromAscii(major.constData()).toInt(); + minorVersion = QString::fromAscii(minor.constData()).toInt(); + statusCode = QString::fromAscii(code.constData()).toInt(); + reasonPhrase = QString::fromAscii(reason.constData()); +} + +qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) +{ + qint64 bytes = 0; + char crlfcrlf[5]; + crlfcrlf[4] = '\0'; + char c = 0; + bool allHeaders = false; + while (!allHeaders && socket->bytesAvailable()) { + if (socket->peek(&c, 1) == 1 && c == '\n') { + // check for possible header endings. As per HTTP rfc, + // the header endings will be marked by CRLFCRLF. But + // we will allow CRLFLF, LFLF & CRLFCRLF + if (fragment.endsWith("\n\r") || fragment.endsWith('\n')) + allHeaders = true; + } + bytes += socket->read(&c, 1); + fragment.append(c); + } + // we received all headers now parse them + if (allHeaders) { + parseHeader(fragment); + state = ReadingDataState; + fragment.clear(); // next fragment + bodyLength = contentLength(); // cache the length + } + return bytes; +} + +void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header) +{ + // see rfc2616, sec 4 for information about HTTP/1.1 headers. + // allows relaxed parsing here, accepts both CRLF & LF line endings + const QByteArrayMatcher lf("\n"); + const QByteArrayMatcher colon(":"); + int i = 0; + while (i < header.count()) { + int j = colon.indexIn(header, i); // field-name + if (j == -1) + break; + const QByteArray field = header.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = lf.indexIn(header, j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && header[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += header.mid(j, length).trimmed(); + j = ++i; + } while (i < header.count() && (header.at(i) == ' ' || header.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + fields.append(qMakePair(field, value)); + } +} + +bool QHttpNetworkReplyPrivate::isChunked() +{ + return headerField("transfer-encoding").toLower().contains("chunked"); +} + +bool QHttpNetworkReplyPrivate::connectionCloseEnabled() +{ + return (headerField("connection").toLower().contains("close") || + headerField("proxy-connection").toLower().contains("close")); +} + +qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QIODevice *out) +{ + qint64 bytes = 0; + if (isChunked()) { + bytes += transferChunked(socket, out); // chunked transfer encoding (rfc 2616, sec 3.6) + } else if (bodyLength > 0) { // we have a Content-Length + bytes += transferRaw(socket, out, bodyLength - contentRead); + if (contentRead + bytes == bodyLength) + state = AllDoneState; + } else { + bytes += transferRaw(socket, out, socket->bytesAvailable()); + } + if (state == AllDoneState) + socket->readAll(); // Read the rest to clean (CRLF) + contentRead += bytes; + return bytes; +} + +qint64 QHttpNetworkReplyPrivate::transferRaw(QIODevice *in, QIODevice *out, qint64 size) +{ + qint64 bytes = 0; + Q_ASSERT(in); + Q_ASSERT(out); + + int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable())); + QByteArray raw(toBeRead, 0); + while (size > 0) { + qint64 read = in->read(raw.data(), raw.size()); + if (read == 0) + return bytes; + // ### error checking here + qint64 written = out->write(raw.data(), read); + if (written == 0) + return bytes; + if (read != written) + qDebug() << "### read" << read << "written" << written; + bytes += read; + size -= read; + out->waitForBytesWritten(-1); // throttle + } + return bytes; + +} + +qint64 QHttpNetworkReplyPrivate::transferChunked(QIODevice *in, QIODevice *out) +{ + qint64 bytes = 0; + while (in->bytesAvailable()) { // while we can read from input + // if we are done with the current chunk, get the size of the new chunk + if (currentChunkRead >= currentChunkSize) { + currentChunkSize = 0; + currentChunkRead = 0; + if (bytes) { + char crlf[2]; + bytes += in->read(crlf, 2); // read the "\r\n" after the chunk + } + bytes += getChunkSize(in, ¤tChunkSize); + if (currentChunkSize == -1) + break; + } + // if the chunk size is 0, end of the stream + if (currentChunkSize == 0) { + state = AllDoneState; + break; + } + // otherwise, read data + qint64 readSize = qMin(in->bytesAvailable(), currentChunkSize - currentChunkRead); + QByteArray buffer(readSize, 0); + qint64 read = in->read(buffer.data(), readSize); + bytes += read; + currentChunkRead += read; + qint64 written = out->write(buffer); + Q_UNUSED(written); // Avoid compile warning when building release + Q_ASSERT(read == written); + // ### error checking here + out->waitForBytesWritten(-1); + } + return bytes; +} + +qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize) +{ + qint64 bytes = 0; + char crlf[2]; + *chunkSize = -1; + int bytesAvailable = in->bytesAvailable(); + while (bytesAvailable > bytes) { + qint64 sniffedBytes = in->peek(crlf, 2); + int fragmentSize = fragment.size(); + // check the next two bytes for a "\r\n", skip blank lines + if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n') + ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n')) + { + bytes += in->read(crlf, 1); // read the \r or \n + if (crlf[0] == '\r') + bytes += in->read(crlf, 1); // read the \n + bool ok = false; + // ignore the chunk-extension + fragment = fragment.mid(0, fragment.indexOf(';')).trimmed(); + *chunkSize = fragment.toLong(&ok, 16); + fragment.clear(); + break; // size done + } else { + // read the fragment to the buffer + char c = 0; + bytes += in->read(&c, 1); + fragment.append(c); + } + } + return bytes; +} + +// QHttpNetworkConnectionPrivate + +typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair; + +class QHttpNetworkConnectionPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QHttpNetworkConnection) +public: + QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt); + ~QHttpNetworkConnectionPrivate(); + void init(); + void connectSignals(QAbstractSocket *socket); + + enum SocketState { + IdleState = 0, // ready to send request + ConnectingState = 1, // connecting to host + WritingState = 2, // writing the data + WaitingState = 4, // waiting for reply + ReadingState = 8, // reading the reply + Wait4AuthState = 0x10, // blocked for send till the current authentication slot is done + BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|Wait4AuthState) + }; + + enum { ChunkSize = 4096 }; + + int indexOf(QAbstractSocket *socket) const; + bool isSocketBusy(QAbstractSocket *socket) const; + bool isSocketWriting(QAbstractSocket *socket) const; + bool isSocketWaiting(QAbstractSocket *socket) const; + bool isSocketReading(QAbstractSocket *socket) const; + + QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request); + void unqueueRequest(QAbstractSocket *socket); + void prepareRequest(HttpMessagePair &request); + bool sendRequest(QAbstractSocket *socket); + void receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply); + void resendCurrentRequest(QAbstractSocket *socket); + void closeChannel(int channel); + void copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy); + + // private slots + void _q_bytesWritten(qint64 bytes); // proceed sending + void _q_readyRead(); // pending data to read + void _q_disconnected(); // disconnected from host + void _q_startNextRequest(); // send the next request from the queue + void _q_restartPendingRequest(); // send the currently blocked request + void _q_connected(); // start sending request + void _q_error(QAbstractSocket::SocketError); // error from socket +#ifndef QT_NO_NETWORKPROXY + void _q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth); // from transparent proxy +#endif + void _q_dataReadyReadNoBuffer(); + void _q_dataReadyReadBuffer(); + + void createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request); + bool ensureConnection(QAbstractSocket *socket); + QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket); + void eraseData(QHttpNetworkReply *reply); +#ifndef QT_NO_COMPRESS + bool expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete); +#endif + void bufferData(HttpMessagePair &request); + void removeReply(QHttpNetworkReply *reply); + + QString hostName; + quint16 port; + bool encrypt; + + struct Channel { + QAbstractSocket *socket; + SocketState state; + QHttpNetworkRequest request; // current request + QHttpNetworkReply *reply; // current reply for this request + qint64 written; + qint64 bytesTotal; + bool resendCurrent; + int lastStatus; // last status received on this channel + bool pendingEncrypt; // for https (send after encrypted) + int reconnectAttempts; // maximum 2 reconnection attempts + QAuthenticatorPrivate::Method authMehtod; + QAuthenticatorPrivate::Method proxyAuthMehtod; + QAuthenticator authenticator; + QAuthenticator proxyAuthenticator; +#ifndef QT_NO_OPENSSL + bool ignoreSSLErrors; +#endif + Channel() :state(IdleState), reply(0), written(0), bytesTotal(0), resendCurrent(false), reconnectAttempts(2), + authMehtod(QAuthenticatorPrivate::None), proxyAuthMehtod(QAuthenticatorPrivate::None) +#ifndef QT_NO_OPENSSL + , ignoreSSLErrors(false) +#endif + {} + }; + static const int channelCount; + Channel channels[2]; // maximum of 2 socket connections to the server + bool pendingAuthSignal; // there is an incomplete authentication signal + bool pendingProxyAuthSignal; // there is an incomplete proxy authentication signal + + void appendData(QHttpNetworkReply &reply, const QByteArray &fragment, bool compressed); + qint64 bytesAvailable(const QHttpNetworkReply &reply, bool compressed = false) const; + qint64 read(QHttpNetworkReply &reply, QByteArray &data, qint64 maxSize, bool compressed); + void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode); + bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend); + void allDone(QAbstractSocket *socket, QHttpNetworkReply *reply); + void handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply); + inline bool emitSignals(QHttpNetworkReply *reply); + inline bool expectContent(QHttpNetworkReply *reply); + +#ifndef QT_NO_OPENSSL + void _q_encrypted(); // start sending request (https) + void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket + QSslConfiguration sslConfiguration(const QHttpNetworkReply &reply) const; +#endif + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy networkProxy; +#endif + + //The request queues + QList<HttpMessagePair> highPriorityQueue; + QList<HttpMessagePair> lowPriorityQueue; +}; + +const int QHttpNetworkConnectionPrivate::channelCount = 2; + +QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt) +: hostName(hostName), port(port), encrypt(encrypt), + pendingAuthSignal(false), pendingProxyAuthSignal(false) +#ifndef QT_NO_NETWORKPROXY + , networkProxy(QNetworkProxy::NoProxy) +#endif +{ +} + +QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate() +{ + for (int i = 0; i < channelCount; ++i) { + channels[i].socket->close(); + delete channels[i].socket; + } +} + +void QHttpNetworkConnectionPrivate::connectSignals(QAbstractSocket *socket) +{ + Q_Q(QHttpNetworkConnection); + + QObject::connect(socket, SIGNAL(bytesWritten(qint64)), + q, SLOT(_q_bytesWritten(qint64)), + Qt::DirectConnection); + QObject::connect(socket, SIGNAL(connected()), + q, SLOT(_q_connected()), + Qt::DirectConnection); + QObject::connect(socket, SIGNAL(readyRead()), + q, SLOT(_q_readyRead()), + Qt::DirectConnection); + QObject::connect(socket, SIGNAL(disconnected()), + q, SLOT(_q_disconnected()), + Qt::DirectConnection); + QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError)), + Qt::DirectConnection); +#ifndef QT_NO_NETWORKPROXY + QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), + q, SLOT(_q_proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), + Qt::DirectConnection); +#endif + +#ifndef QT_NO_OPENSSL + QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); + QObject::connect(sslSocket, SIGNAL(encrypted()), + q, SLOT(_q_encrypted()), + Qt::DirectConnection); + QObject::connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError>&)), + q, SLOT(_q_sslErrors(const QList<QSslError>&)), + Qt::DirectConnection); +#endif +} + +void QHttpNetworkConnectionPrivate::init() +{ + for (int i = 0; i < channelCount; ++i) { +#ifndef QT_NO_OPENSSL + channels[i].socket = new QSslSocket; +#else + channels[i].socket = new QTcpSocket; +#endif + connectSignals(channels[i].socket); + } +} + +int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const +{ + for (int i = 0; i < channelCount; ++i) + if (channels[i].socket == socket) + return i; + return -1; +} + +bool QHttpNetworkConnectionPrivate::isSocketBusy(QAbstractSocket *socket) const +{ + int i = indexOf(socket); + return (channels[i].state & BusyState); +} + +bool QHttpNetworkConnectionPrivate::isSocketWriting(QAbstractSocket *socket) const +{ + int i = indexOf(socket); + return (i != -1 && (channels[i].state & WritingState)); +} + +bool QHttpNetworkConnectionPrivate::isSocketWaiting(QAbstractSocket *socket) const +{ + int i = indexOf(socket); + return (i != -1 && (channels[i].state & WaitingState)); +} + +bool QHttpNetworkConnectionPrivate::isSocketReading(QAbstractSocket *socket) const +{ + int i = indexOf(socket); + return (i != -1 && (channels[i].state & ReadingState)); +} + + +void QHttpNetworkConnectionPrivate::appendData(QHttpNetworkReply &reply, const QByteArray &fragment, bool compressed) +{ + QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData; + ba->append(fragment); + return; +} + +qint64 QHttpNetworkConnectionPrivate::bytesAvailable(const QHttpNetworkReply &reply, bool compressed) const +{ + const QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData; + return ba->size(); +} + +qint64 QHttpNetworkConnectionPrivate::read(QHttpNetworkReply &reply, QByteArray &data, qint64 maxSize, bool compressed) +{ + QByteArray *ba = (compressed) ? &reply.d_func()->compressedData : &reply.d_func()->responseData; + if (maxSize == -1 || maxSize >= ba->size()) { + // read the whole data + data = *ba; + ba->clear(); + } else { + // read only the requested length + data = ba->mid(0, maxSize); + ba->remove(0, maxSize); + } + return data.size(); +} + +void QHttpNetworkConnectionPrivate::eraseData(QHttpNetworkReply *reply) +{ + reply->d_func()->compressedData.clear(); + reply->d_func()->responseData.clear(); +} + +void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) +{ + QHttpNetworkRequest &request = messagePair.first; + QHttpNetworkReply *reply = messagePair.second; + + // add missing fields for the request + QByteArray value; + // check if Content-Length is provided + QIODevice *data = request.data(); + if (data && request.contentLength() == -1) { + if (!data->isSequential()) + request.setContentLength(data->size()); + else + bufferData(messagePair); // ### or do chunked upload + } + // set the Connection/Proxy-Connection: Keep-Alive headers +#ifndef QT_NO_NETWORKPROXY + if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) { + value = request.headerField("proxy-connection"); + if (value.isEmpty()) + request.setHeaderField("Proxy-Connection", "Keep-Alive"); + } else { +#endif + value = request.headerField("connection"); + if (value.isEmpty()) + request.setHeaderField("Connection", "Keep-Alive"); +#ifndef QT_NO_NETWORKPROXY + } +#endif + // set the gzip header + value = request.headerField("accept-encoding"); + if (value.isEmpty()) { +#ifndef QT_NO_COMPRESS + request.setHeaderField("Accept-Encoding", "gzip"); + request.d->autoDecompress = true; +#else + // if zlib is not available set this to false always + request.d->autoDecompress = false; +#endif + } + // set the User Agent + value = request.headerField("user-agent"); + if (value.isEmpty()) + request.setHeaderField("User-Agent", "Mozilla/5.0"); + // set the host + value = request.headerField("host"); + if (value.isEmpty()) { + QByteArray host = QUrl::toAce(hostName); + + int port = request.url().port(); + if (port != -1) { + host += ':'; + host += QByteArray::number(port); + } + + request.setHeaderField("Host", host); + } + + reply->d_func()->requestIsPrepared = true; +} + +bool QHttpNetworkConnectionPrivate::ensureConnection(QAbstractSocket *socket) +{ + // make sure that this socket is in a connected state, if not initiate + // connection to the host. + if (socket->state() != QAbstractSocket::ConnectedState) { + // connect to the host if not already connected. + int index = indexOf(socket); + channels[index].state = ConnectingState; + channels[index].pendingEncrypt = encrypt; + + // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done" + // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the + // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not + // check the "phase" for generating the Authorization header. NTLM authentication is a two stage + // process & needs the "phase". To make sure the QAuthenticator uses the current username/password + // the phase is reset to Start. + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[index].authenticator); + if (priv && priv->phase == QAuthenticatorPrivate::Done) + priv->phase = QAuthenticatorPrivate::Start; + priv = QAuthenticatorPrivate::getPrivate(channels[index].proxyAuthenticator); + if (priv && priv->phase == QAuthenticatorPrivate::Done) + priv->phase = QAuthenticatorPrivate::Start; + + QString connectHost = hostName; + qint16 connectPort = port; + +#ifndef QT_NO_NETWORKPROXY + // HTTPS always use transparent proxy. + if (networkProxy.type() != QNetworkProxy::NoProxy && !encrypt) { + connectHost = networkProxy.hostName(); + connectPort = networkProxy.port(); + } +#endif + if (encrypt) { +#ifndef QT_NO_OPENSSL + QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket); + sslSocket->connectToHostEncrypted(connectHost, connectPort); + if (channels[index].ignoreSSLErrors) + sslSocket->ignoreSslErrors(); +#else + emitReplyError(socket, channels[index].reply, QNetworkReply::ProtocolUnknownError); +#endif + } else { + socket->connectToHost(connectHost, connectPort); + } + return false; + } + return true; +} + + +bool QHttpNetworkConnectionPrivate::sendRequest(QAbstractSocket *socket) +{ + Q_Q(QHttpNetworkConnection); + + int i = indexOf(socket); + switch (channels[i].state) { + case IdleState: { // write the header + if (!ensureConnection(socket)) { + // wait for the connection (and encryption) to be done + // sendRequest will be called again from either + // _q_connected or _q_encrypted + return false; + } + channels[i].written = 0; // excluding the header + channels[i].bytesTotal = 0; + if (channels[i].reply) { + channels[i].reply->d_func()->clear(); + channels[i].reply->d_func()->connection = q; + channels[i].reply->d_func()->autoDecompress = channels[i].request.d->autoDecompress; + } + channels[i].state = WritingState; + channels[i].pendingEncrypt = false; + // if the url contains authentication parameters, use the new ones + // both channels will use the new authentication parameters + if (!channels[i].request.url().userInfo().isEmpty()) { + QUrl url = channels[i].request.url(); + QAuthenticator &auth = channels[i].authenticator; + if (url.userName() != auth.user() + || (!url.password().isEmpty() && url.password() != auth.password())) { + auth.setUser(url.userName()); + auth.setPassword(url.password()); + copyCredentials(i, &auth, false); + } + // clear the userinfo, since we use the same request for resending + // userinfo in url can conflict with the one in the authenticator + url.setUserInfo(QString()); + channels[i].request.setUrl(url); + } + createAuthorization(socket, channels[i].request); +#ifndef QT_NO_NETWORKPROXY + QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request, + (networkProxy.type() != QNetworkProxy::NoProxy)); +#else + QByteArray header = QHttpNetworkRequestPrivate::header(channels[i].request, + false); +#endif + socket->write(header); + QIODevice *data = channels[i].request.d->data; + QHttpNetworkReply *reply = channels[i].reply; + if (reply && reply->d_func()->requestDataBuffer.size()) + data = &channels[i].reply->d_func()->requestDataBuffer; + if (data && (data->isOpen() || data->open(QIODevice::ReadOnly))) { + if (data->isSequential()) { + channels[i].bytesTotal = -1; + QObject::connect(data, SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadNoBuffer())); + QObject::connect(data, SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadNoBuffer())); + } else { + channels[i].bytesTotal = data->size(); + } + } else { + channels[i].state = WaitingState; + break; + } + // write the initial chunk together with the headers + // fall through + } + case WritingState: { // write the data + QIODevice *data = channels[i].request.d->data; + if (channels[i].reply->d_func()->requestDataBuffer.size()) + data = &channels[i].reply->d_func()->requestDataBuffer; + if (!data || channels[i].bytesTotal == channels[i].written) { + channels[i].state = WaitingState; // now wait for response + break; + } + + QByteArray chunk; + chunk.resize(ChunkSize); + qint64 readSize = data->read(chunk.data(), ChunkSize); + if (readSize == -1) { + // source has reached EOF + channels[i].state = WaitingState; // now wait for response + } else if (readSize > 0) { + // source gave us something useful + channels[i].written += socket->write(chunk.data(), readSize); + if (channels[i].reply) + emit channels[i].reply->dataSendProgress(channels[i].written, channels[i].bytesTotal); + } + break; + } + case WaitingState: + case ReadingState: + case Wait4AuthState: + // ignore _q_bytesWritten in these states + // fall through + default: + break; + } + return true; +} + +bool QHttpNetworkConnectionPrivate::emitSignals(QHttpNetworkReply *reply) +{ + // for 401 & 407 don't emit the data signals. Content along with these + // responses are send only if the authentication fails. + return (reply && reply->d_func()->statusCode != 401 && reply->d_func()->statusCode != 407); +} + +bool QHttpNetworkConnectionPrivate::expectContent(QHttpNetworkReply *reply) +{ + // check whether we can expect content after the headers (rfc 2616, sec4.4) + if (!reply) + return false; + if ((reply->d_func()->statusCode >= 100 && reply->d_func()->statusCode < 200) + || reply->d_func()->statusCode == 204 || reply->d_func()->statusCode == 304) + return false; + if (reply->d_func()->request.operation() == QHttpNetworkRequest::Head) + return !emitSignals(reply); + if (reply->d_func()->contentLength() == 0) + return false; + return true; +} + +void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, + QHttpNetworkReply *reply, + QNetworkReply::NetworkError errorCode) +{ + Q_Q(QHttpNetworkConnection); + if (socket && reply) { + // this error matters only to this reply + reply->d_func()->errorString = errorDetail(errorCode, socket); + emit reply->finishedWithError(errorCode, reply->d_func()->errorString); + int i = indexOf(socket); + // remove the corrupt data if any + eraseData(channels[i].reply); + closeChannel(i); + // send the next request + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + } +} + +#ifndef QT_NO_COMPRESS +bool QHttpNetworkConnectionPrivate::expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete) +{ + Q_ASSERT(socket); + Q_ASSERT(reply); + + qint64 total = bytesAvailable(*reply, true); + if (total >= CHUNK || dataComplete) { + int i = indexOf(socket); + // uncompress the data + QByteArray content, inflated; + read(*reply, content, -1, true); + int ret = Z_OK; + if (content.size()) + ret = reply->d_func()->gunzipBodyPartially(content, inflated); + int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK; + if (ret >= retCheck) { + if (inflated.size()) { + reply->d_func()->totalProgress += inflated.size(); + appendData(*reply, inflated, false); + if (emitSignals(reply)) { + emit reply->readyRead(); + // make sure that the reply is valid + if (channels[i].reply != reply) + return true; + emit reply->dataReadProgress(reply->d_func()->totalProgress, 0); + // make sure that the reply is valid + if (channels[i].reply != reply) + return true; + + } + } + } else { + emitReplyError(socket, reply, QNetworkReply::ProtocolFailure); + return false; + } + } + return true; +} +#endif + +void QHttpNetworkConnectionPrivate::receiveReply(QAbstractSocket *socket, QHttpNetworkReply *reply) +{ + Q_ASSERT(socket); + + Q_Q(QHttpNetworkConnection); + qint64 bytes = 0; + QAbstractSocket::SocketState state = socket->state(); + int i = indexOf(socket); + + // connection might be closed to signal the end of data + if (state == QAbstractSocket::UnconnectedState) { + if (!socket->bytesAvailable()) { + if (reply && reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { + reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + channels[i].state = IdleState; + allDone(socket, reply); + } else { + // try to reconnect/resend before sending an error. + if (channels[i].reconnectAttempts-- > 0) { + resendCurrentRequest(socket); + } else { + reply->d_func()->errorString = errorDetail(QNetworkReply::RemoteHostClosedError, socket); + emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString); + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + } + } + } + } + + // read loop for the response + while (socket->bytesAvailable()) { + QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState; + switch (state) { + case QHttpNetworkReplyPrivate::NothingDoneState: + case QHttpNetworkReplyPrivate::ReadingStatusState: + bytes += reply->d_func()->readStatus(socket); + channels[i].lastStatus = reply->d_func()->statusCode; + break; + case QHttpNetworkReplyPrivate::ReadingHeaderState: + bytes += reply->d_func()->readHeader(socket); + if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) { + if (reply->d_func()->isGzipped() && reply->d_func()->autoDecompress) { + // remove the Content-Length from header + reply->d_func()->removeAutoDecompressHeader(); + } else { + reply->d_func()->autoDecompress = false; + } + if (reply && reply->d_func()->statusCode == 100) { + reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState; + break; // ignore + } + if (emitSignals(reply)) + emit reply->headerChanged(); + if (!expectContent(reply)) { + reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; + channels[i].state = IdleState; + allDone(socket, reply); + return; + } + } + break; + case QHttpNetworkReplyPrivate::ReadingDataState: { + QBuffer fragment; + fragment.open(QIODevice::WriteOnly); + bytes = reply->d_func()->readBody(socket, &fragment); + if (bytes) { + appendData(*reply, fragment.data(), reply->d_func()->autoDecompress); + if (!reply->d_func()->autoDecompress) { + reply->d_func()->totalProgress += fragment.size(); + if (emitSignals(reply)) { + emit reply->readyRead(); + // make sure that the reply is valid + if (channels[i].reply != reply) + return; + emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength); + // make sure that the reply is valid + if (channels[i].reply != reply) + return; + } + } +#ifndef QT_NO_COMPRESS + else if (!expand(socket, reply, false)) { // expand a chunk if possible + return; // ### expand failed + } +#endif + } + if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) + break; + // everything done, fall through + } + case QHttpNetworkReplyPrivate::AllDoneState: + channels[i].state = IdleState; + allDone(socket, reply); + break; + default: + break; + } + } +} + +void QHttpNetworkConnectionPrivate::allDone(QAbstractSocket *socket, QHttpNetworkReply *reply) +{ +#ifndef QT_NO_COMPRESS + // expand the whole data. + if (expectContent(reply) && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) + expand(socket, reply, true); // ### if expand returns false, its an error +#endif + // while handling 401 & 407, we might reset the status code, so save this. + bool emitFinished = emitSignals(reply); + handleStatus(socket, reply); + // ### at this point there should be no more data on the socket + // close if server requested + int i = indexOf(socket); + if (reply->d_func()->connectionCloseEnabled()) + closeChannel(i); + // queue the finished signal, this is required since we might send new requests from + // slot connected to it. The socket will not fire readyRead signal, if we are already + // in the slot connected to readyRead + if (emitFinished) + QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection); + // reset the reconnection attempts after we receive a complete reply. + // in case of failures, each channel will attempt two reconnects before emitting error. + channels[i].reconnectAttempts = 2; +} + +void QHttpNetworkConnectionPrivate::handleStatus(QAbstractSocket *socket, QHttpNetworkReply *reply) +{ + Q_ASSERT(socket); + Q_ASSERT(reply); + + Q_Q(QHttpNetworkConnection); + + int statusCode = reply->statusCode(); + bool resend = false; + + switch (statusCode) { + case 401: + case 407: + handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend); + if (resend) { + eraseData(reply); + sendRequest(socket); + } + break; + default: + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + } +} + +void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy) +{ + Q_ASSERT(auth); + + // select another channel + QAuthenticator* otherAuth = 0; + for (int i = 0; i < channelCount; ++i) { + if (i == fromChannel) + continue; + if (isProxy) + otherAuth = &channels[i].proxyAuthenticator; + else + otherAuth = &channels[i].authenticator; + // if the credentials are different, copy them + if (otherAuth->user().compare(auth->user())) + otherAuth->setUser(auth->user()); + if (otherAuth->password().compare(auth->password())) + otherAuth->setPassword(auth->password()); + } +} + + +bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, + bool isProxy, bool &resend) +{ + Q_ASSERT(socket); + Q_ASSERT(reply); + + Q_Q(QHttpNetworkConnection); + + resend = false; + //create the response header to be used with QAuthenticatorPrivate. + QHttpResponseHeader responseHeader; + QList<QPair<QByteArray, QByteArray> > fields = reply->header(); + QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin(); + while (it != fields.constEnd()) { + responseHeader.addValue(QString::fromLatin1(it->first), QString::fromUtf8(it->second)); + it++; + } + //find out the type of authentication protocol requested. + QAuthenticatorPrivate::Method authMethod = reply->d_func()->authenticationMethod(isProxy); + if (authMethod != QAuthenticatorPrivate::None) { + int i = indexOf(socket); + //Use a single authenticator for all domains. ### change later to use domain/realm + QAuthenticator* auth = 0; + if (isProxy) { + auth = &channels[i].proxyAuthenticator; + channels[i].proxyAuthMehtod = authMethod; + } else { + auth = &channels[i].authenticator; + channels[i].authMehtod = authMethod; + } + //proceed with the authentication. + if (auth->isNull()) + auth->detach(); + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth); + priv->parseHttpResponse(responseHeader, isProxy); + + if (priv->phase == QAuthenticatorPrivate::Done) { + if ((isProxy && pendingProxyAuthSignal) ||(!isProxy && pendingAuthSignal)) { + // drop the request + eraseData(channels[i].reply); + closeChannel(i); + channels[i].lastStatus = 0; + channels[i].state = Wait4AuthState; + return false; + } + // cannot use this socket until the slot returns + channels[i].state = WaitingState; + socket->blockSignals(true); + if (!isProxy) { + pendingAuthSignal = true; + emit q->authenticationRequired(reply->request(), auth, q); + pendingAuthSignal = false; +#ifndef QT_NO_NETWORKPROXY + } else { + pendingProxyAuthSignal = true; + emit q->proxyAuthenticationRequired(networkProxy, auth, q); + pendingProxyAuthSignal = false; +#endif + } + socket->blockSignals(false); + // socket free to use + channels[i].state = IdleState; + if (priv->phase != QAuthenticatorPrivate::Done) { + // send any pending requests + copyCredentials(i, auth, isProxy); + QMetaObject::invokeMethod(q, "_q_restartPendingRequest", Qt::QueuedConnection); + } + } + // changing values in QAuthenticator will reset the 'phase' + if (priv->phase == QAuthenticatorPrivate::Done) { + // authentication is cancelled, send the current contents to the user. + emit channels[i].reply->headerChanged(); + emit channels[i].reply->readyRead(); + emit channels[i].reply->finished(); + QNetworkReply::NetworkError errorCode = + isProxy + ? QNetworkReply::ProxyAuthenticationRequiredError + : QNetworkReply::AuthenticationRequiredError; + reply->d_func()->errorString = errorDetail(errorCode, socket); + emit q->error(errorCode, reply->d_func()->errorString); + emit channels[i].reply->finished(); + // ### at this point the reply could be deleted + socket->close(); + // remove pending request on the other channels + for (int j = 0; j < channelCount; ++j) { + if (j != i && channels[j].state == Wait4AuthState) + channels[j].state = IdleState; + } + return true; + } + //resend the request + resend = true; + return true; + } + return false; +} + +void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request) +{ + Q_ASSERT(socket); + + int i = indexOf(socket); + if (channels[i].authMehtod != QAuthenticatorPrivate::None) { + if (!(channels[i].authMehtod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 401)) { + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator); + if (priv && priv->method != QAuthenticatorPrivate::None) { + QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); + request.setHeaderField("authorization", response); + } + } + } + if (channels[i].proxyAuthMehtod != QAuthenticatorPrivate::None) { + if (!(channels[i].proxyAuthMehtod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) { + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator); + if (priv && priv->method != QAuthenticatorPrivate::None) { + QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); + request.setHeaderField("proxy-authorization", response); + } + } + } +} + +QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request) +{ + Q_Q(QHttpNetworkConnection); + + // The reply component of the pair is created initially. + QHttpNetworkReply *reply = new QHttpNetworkReply(request.url()); + reply->setRequest(request); + reply->d_func()->connection = q; + HttpMessagePair pair = qMakePair(request, reply); + + switch (request.priority()) { + case QHttpNetworkRequest::HighPriority: + highPriorityQueue.prepend(pair); + break; + case QHttpNetworkRequest::NormalPriority: + case QHttpNetworkRequest::LowPriority: + lowPriorityQueue.prepend(pair); + break; + } + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + return reply; +} + +void QHttpNetworkConnectionPrivate::unqueueRequest(QAbstractSocket *socket) +{ + Q_ASSERT(socket); + + int i = indexOf(socket); + + if (!highPriorityQueue.isEmpty()) { + for (int j = highPriorityQueue.count() - 1; j >= 0; --j) { + HttpMessagePair &messagePair = highPriorityQueue[j]; + if (!messagePair.second->d_func()->requestIsPrepared) + prepareRequest(messagePair); + if (!messagePair.second->d_func()->requestIsBuffering) { + channels[i].request = messagePair.first; + channels[i].reply = messagePair.second; + sendRequest(socket); + highPriorityQueue.removeAt(j); + return; + } + } + } + + if (!lowPriorityQueue.isEmpty()) { + for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) { + HttpMessagePair &messagePair = lowPriorityQueue[j]; + if (!messagePair.second->d_func()->requestIsPrepared) + prepareRequest(messagePair); + if (!messagePair.second->d_func()->requestIsBuffering) { + channels[i].request = messagePair.first; + channels[i].reply = messagePair.second; + sendRequest(socket); + lowPriorityQueue.removeAt(j); + return; + } + } + } +} + +void QHttpNetworkConnectionPrivate::closeChannel(int channel) +{ + QAbstractSocket *socket = channels[channel].socket; + socket->blockSignals(true); + socket->close(); + socket->blockSignals(false); + channels[channel].state = IdleState; +} + +void QHttpNetworkConnectionPrivate::resendCurrentRequest(QAbstractSocket *socket) +{ + Q_Q(QHttpNetworkConnection); + Q_ASSERT(socket); + int i = indexOf(socket); + closeChannel(i); + channels[i].resendCurrent = true; + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); +} + +QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket* socket) +{ + Q_ASSERT(socket); + + QString errorString; + switch (errorCode) { + case QNetworkReply::HostNotFoundError: + errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QHttp", "Host %1 not found")) + .arg(socket->peerName()); + break; + case QNetworkReply::ConnectionRefusedError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Connection refused")); + break; + case QNetworkReply::RemoteHostClosedError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Connection closed")); + break; + case QNetworkReply::TimeoutError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTP request failed")); + break; + case QNetworkReply::ProxyAuthenticationRequiredError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Proxy requires authentication")); + break; + case QNetworkReply::AuthenticationRequiredError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Host requires authentication")); + break; + case QNetworkReply::ProtocolFailure: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Data corrupted")); + break; + case QNetworkReply::ProtocolUnknownError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "Unknown protocol specified")); + break; + case QNetworkReply::SslHandshakeFailedError: + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "SSL handshake failed")); + break; + default: + // all other errors are treated as QNetworkReply::UnknownNetworkError + errorString = QLatin1String(QT_TRANSLATE_NOOP("QHttp", "HTTP request failed")); + break; + } + return errorString; +} + +void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) +{ + Q_Q(QHttpNetworkConnection); + + // remove the from active list. + for (int i = 0; i < channelCount; ++i) { + if (channels[i].reply == reply) { + channels[i].reply = 0; + closeChannel(i); + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + return; + } + } + // remove from the high priority queue + if (!highPriorityQueue.isEmpty()) { + for (int j = highPriorityQueue.count() - 1; j >= 0; --j) { + HttpMessagePair messagePair = highPriorityQueue.at(j); + if (messagePair.second == reply) { + highPriorityQueue.removeAt(j); + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + return; + } + } + } + // remove from the low priority queue + if (!lowPriorityQueue.isEmpty()) { + for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) { + HttpMessagePair messagePair = lowPriorityQueue.at(j); + if (messagePair.second == reply) { + lowPriorityQueue.removeAt(j); + QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection); + return; + } + } + } +} + + +//private slots +void QHttpNetworkConnectionPrivate::_q_readyRead() +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; // ### error + if (isSocketWaiting(socket) || isSocketReading(socket)) { + int i = indexOf(socket); + channels[i].state = ReadingState; + if (channels[i].reply) + receiveReply(socket, channels[i].reply); + } + // ### error +} + +void QHttpNetworkConnectionPrivate::_q_bytesWritten(qint64 bytes) +{ + Q_UNUSED(bytes); + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; // ### error + if (isSocketWriting(socket)) + sendRequest(socket); + // otherwise we do nothing +} + +void QHttpNetworkConnectionPrivate::_q_disconnected() +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; // ### error + // read the available data before closing + int i = indexOf(socket); + if (isSocketWaiting(socket) || isSocketReading(socket)) { + channels[i].state = ReadingState; + if (channels[i].reply) + receiveReply(socket, channels[i].reply); + } + channels[i].state = IdleState; +} + +void QHttpNetworkConnectionPrivate::_q_startNextRequest() +{ + // send the current request again + if (channels[0].resendCurrent || channels[1].resendCurrent) { + int i = channels[0].resendCurrent ? 0:1; + QAbstractSocket *socket = channels[i].socket; + channels[i].resendCurrent = false; + channels[i].state = IdleState; + if (channels[i].reply) + sendRequest(socket); + return; + } + // send the request using the idle socket + QAbstractSocket *socket = channels[0].socket; + if (isSocketBusy(socket)) { + socket = (isSocketBusy(channels[1].socket) ? 0 :channels[1].socket); + } + + if (!socket) { + return; // this will be called after finishing current request. + } + unqueueRequest(socket); +} + +void QHttpNetworkConnectionPrivate::_q_restartPendingRequest() +{ + // send the request using the idle socket + for (int i = 0 ; i < channelCount; ++i) { + QAbstractSocket *socket = channels[i].socket; + if (channels[i].state == Wait4AuthState) { + channels[i].state = IdleState; + if (channels[i].reply) + sendRequest(socket); + } + } +} + +void QHttpNetworkConnectionPrivate::_q_connected() +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; // ### error + int i = indexOf(socket); + // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again! + //channels[i].reconnectAttempts = 2; + if (!channels[i].pendingEncrypt) { + channels[i].state = IdleState; + if (channels[i].reply) + sendRequest(socket); + else + closeChannel(i); + } +} + + +void QHttpNetworkConnectionPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; + bool send2Reply = false; + int i = indexOf(socket); + QNetworkReply::NetworkError errorCode = QNetworkReply::UnknownNetworkError; + + switch (socketError) { + case QAbstractSocket::HostNotFoundError: + errorCode = QNetworkReply::HostNotFoundError; + break; + case QAbstractSocket::ConnectionRefusedError: + errorCode = QNetworkReply::ConnectionRefusedError; + break; + case QAbstractSocket::RemoteHostClosedError: + // try to reconnect/resend before sending an error. + // while "Reading" the _q_disconnected() will handle this. + if (channels[i].state != IdleState && channels[i].state != ReadingState) { + if (channels[i].reconnectAttempts-- > 0) { + resendCurrentRequest(socket); + return; + } else { + send2Reply = true; + errorCode = QNetworkReply::RemoteHostClosedError; + } + } else { + return; + } + break; + case QAbstractSocket::SocketTimeoutError: + // try to reconnect/resend before sending an error. + if (channels[i].state == WritingState && (channels[i].reconnectAttempts-- > 0)) { + resendCurrentRequest(socket); + return; + } + send2Reply = true; + errorCode = QNetworkReply::TimeoutError; + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + errorCode = QNetworkReply::ProxyAuthenticationRequiredError; + break; + case QAbstractSocket::SslHandshakeFailedError: + errorCode = QNetworkReply::SslHandshakeFailedError; + break; + default: + // all other errors are treated as NetworkError + errorCode = QNetworkReply::UnknownNetworkError; + break; + } + QPointer<QObject> that = q; + QString errorString = errorDetail(errorCode, socket); + if (send2Reply) { + if (channels[i].reply) { + channels[i].reply->d_func()->errorString = errorString; + // this error matters only to this reply + emit channels[i].reply->finishedWithError(errorCode, errorString); + } + // send the next request + QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection); + } else { + // the failure affects all requests. + emit q->error(errorCode, errorString); + } + if (that) //signals make enter the event loop + closeChannel(i); +} + +#ifndef QT_NO_NETWORKPROXY +void QHttpNetworkConnectionPrivate::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth) +{ + Q_Q(QHttpNetworkConnection); + emit q->proxyAuthenticationRequired(proxy, auth, q); +} +#endif + +void QHttpNetworkConnectionPrivate::_q_dataReadyReadNoBuffer() +{ + Q_Q(QHttpNetworkConnection); + // data emitted either readyRead() + // find out which channel it is for + QIODevice *sender = qobject_cast<QIODevice *>(q->sender()); + + // won't match anything if the qobject_cast above failed + for (int i = 0; i < channelCount; ++i) { + if (sender == channels[i].request.data()) { + sendRequest(channels[i].socket); + break; + } + } +} + +void QHttpNetworkConnectionPrivate::_q_dataReadyReadBuffer() +{ + Q_Q(QHttpNetworkConnection); + QIODevice *sender = qobject_cast<QIODevice *>(q->sender()); + HttpMessagePair *thePair = 0; + for (int i = 0; !thePair && i < lowPriorityQueue.size(); ++i) + if (lowPriorityQueue.at(i).first.data() == sender) + thePair = &lowPriorityQueue[i]; + + for (int i = 0; !thePair && i < highPriorityQueue.size(); ++i) + if (highPriorityQueue.at(i).first.data() == sender) + thePair = &highPriorityQueue[i]; + + if (thePair) { + bufferData(*thePair); + + // are we finished buffering? + if (!thePair->second->d_func()->requestIsBuffering) + _q_startNextRequest(); + } +} + +void QHttpNetworkConnectionPrivate::bufferData(HttpMessagePair &messagePair) +{ + Q_Q(QHttpNetworkConnection); + QHttpNetworkRequest &request = messagePair.first; + QHttpNetworkReply *reply = messagePair.second; + Q_ASSERT(request.data()); + if (!reply->d_func()->requestIsBuffering) { // first time + QObject::connect(request.data(), SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadBuffer())); + QObject::connect(request.data(), SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadBuffer())); + reply->d_func()->requestIsBuffering = true; + reply->d_func()->requestDataBuffer.open(QIODevice::ReadWrite); + } + + // always try to read at least one byte + // ### FIXME! use a QRingBuffer + qint64 bytesToRead = qMax<qint64>(1, request.data()->bytesAvailable()); + QByteArray newData; + newData.resize(bytesToRead); + qint64 bytesActuallyRead = request.data()->read(newData.data(), bytesToRead); + + if (bytesActuallyRead > 0) { + // we read something + newData.chop(bytesToRead - bytesActuallyRead); + reply->d_func()->requestDataBuffer.write(newData); + } else if (bytesActuallyRead == -1) { // last time + QObject::disconnect(request.data(), SIGNAL(readyRead()), q, SLOT(_q_dataReadyReadBuffer())); + QObject::disconnect(request.data(), SIGNAL(readChannelFinished()), q, SLOT(_q_dataReadyReadBuffer())); + + request.setContentLength(reply->d_func()->requestDataBuffer.size()); + reply->d_func()->requestDataBuffer.seek(0); + reply->d_func()->requestIsBuffering = false; + } +} + +// QHttpNetworkConnection + +QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent) + : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) +{ + Q_D(QHttpNetworkConnection); + d->init(); +} + +QHttpNetworkConnection::~QHttpNetworkConnection() +{ +} + +QString QHttpNetworkConnection::hostName() const +{ + Q_D(const QHttpNetworkConnection); + return d->hostName; +} + +quint16 QHttpNetworkConnection::port() const +{ + Q_D(const QHttpNetworkConnection); + return d->port; +} + +QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request) +{ + Q_D(QHttpNetworkConnection); + return d->queueRequest(request); +} + +void QHttpNetworkConnection::enableEncryption() +{ + Q_D(QHttpNetworkConnection); + d->encrypt = true; +} + +bool QHttpNetworkConnection::isEncrypted() const +{ + Q_D(const QHttpNetworkConnection); + return d->encrypt; +} + +void QHttpNetworkConnection::setProxyAuthentication(QAuthenticator *authenticator) +{ + Q_D(QHttpNetworkConnection); + for (int i = 0; i < d->channelCount; ++i) + d->channels[i].proxyAuthenticator = *authenticator; +} + +void QHttpNetworkConnection::setAuthentication(const QString &domain, QAuthenticator *authenticator) +{ + Q_UNUSED(domain); // ### domain ? + Q_D(QHttpNetworkConnection); + for (int i = 0; i < d->channelCount; ++i) + d->channels[i].authenticator = *authenticator; +} + +#ifndef QT_NO_NETWORKPROXY +void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QHttpNetworkConnection); + d->networkProxy = networkProxy; + // update the authenticator + if (!d->networkProxy.user().isEmpty()) { + for (int i = 0; i < d->channelCount; ++i) { + d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user()); + d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password()); + } + } +} + +QNetworkProxy QHttpNetworkConnection::cacheProxy() const +{ + Q_D(const QHttpNetworkConnection); + return d->networkProxy; +} + +void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QHttpNetworkConnection); + for (int i = 0; i < d->channelCount; ++i) + d->channels[i].socket->setProxy(networkProxy); +} + +QNetworkProxy QHttpNetworkConnection::transparentProxy() const +{ + Q_D(const QHttpNetworkConnection); + return d->channels[0].socket->proxy(); +} +#endif + +// QHttpNetworkRequest + +QHttpNetworkRequest::QHttpNetworkRequest(const QUrl &url, Operation operation, Priority priority) + : d(new QHttpNetworkRequestPrivate(operation, priority, url)) +{ +} + +QHttpNetworkRequest::QHttpNetworkRequest(const QHttpNetworkRequest &other) + : QHttpNetworkHeader(other), d(other.d) +{ +} + +QHttpNetworkRequest::~QHttpNetworkRequest() +{ +} + +QUrl QHttpNetworkRequest::url() const +{ + return d->url; +} +void QHttpNetworkRequest::setUrl(const QUrl &url) +{ + d->url = url; +} + +qint64 QHttpNetworkRequest::contentLength() const +{ + return d->contentLength(); +} + +void QHttpNetworkRequest::setContentLength(qint64 length) +{ + d->setContentLength(length); +} + +QList<QPair<QByteArray, QByteArray> > QHttpNetworkRequest::header() const +{ + return d->fields; +} + +QByteArray QHttpNetworkRequest::headerField(const QByteArray &name, const QByteArray &defaultValue) const +{ + return d->headerField(name, defaultValue); +} + +void QHttpNetworkRequest::setHeaderField(const QByteArray &name, const QByteArray &data) +{ + d->setHeaderField(name, data); +} + +QHttpNetworkRequest &QHttpNetworkRequest::operator=(const QHttpNetworkRequest &other) +{ + d = other.d; + return *this; +} + +bool QHttpNetworkRequest::operator==(const QHttpNetworkRequest &other) const +{ + return d->operator==(*other.d); +} + +QHttpNetworkRequest::Operation QHttpNetworkRequest::operation() const +{ + return d->operation; +} + +void QHttpNetworkRequest::setOperation(Operation operation) +{ + d->operation = operation; +} + +QHttpNetworkRequest::Priority QHttpNetworkRequest::priority() const +{ + return d->priority; +} + +void QHttpNetworkRequest::setPriority(Priority priority) +{ + d->priority = priority; +} + +QIODevice *QHttpNetworkRequest::data() const +{ + return d->data; +} + +void QHttpNetworkRequest::setData(QIODevice *data) +{ + d->data = data; +} + +int QHttpNetworkRequest::majorVersion() const +{ + return 1; +} + +int QHttpNetworkRequest::minorVersion() const +{ + return 1; +} + +// QHttpNetworkReply + +QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent) + : QObject(*new QHttpNetworkReplyPrivate(url), parent) +{ +} + +QHttpNetworkReply::~QHttpNetworkReply() +{ + Q_D(QHttpNetworkReply); + if (d->connection) { + d->connection->d_func()->removeReply(this); + } +} + +QUrl QHttpNetworkReply::url() const +{ + return d_func()->url; +} +void QHttpNetworkReply::setUrl(const QUrl &url) +{ + Q_D(QHttpNetworkReply); + d->url = url; +} + +qint64 QHttpNetworkReply::contentLength() const +{ + return d_func()->contentLength(); +} + +void QHttpNetworkReply::setContentLength(qint64 length) +{ + Q_D(QHttpNetworkReply); + d->setContentLength(length); +} + +QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const +{ + return d_func()->fields; +} + +QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const +{ + return d_func()->headerField(name, defaultValue); +} + +void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray &data) +{ + Q_D(QHttpNetworkReply); + d->setHeaderField(name, data); +} + +void QHttpNetworkReply::parseHeader(const QByteArray &header) +{ + Q_D(QHttpNetworkReply); + d->parseHeader(header); +} + +QHttpNetworkRequest QHttpNetworkReply::request() const +{ + return d_func()->request; +} + +void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request) +{ + Q_D(QHttpNetworkReply); + d->request = request; +} + +int QHttpNetworkReply::statusCode() const +{ + return d_func()->statusCode; +} + +void QHttpNetworkReply::setStatusCode(int code) +{ + Q_D(QHttpNetworkReply); + d->statusCode = code; +} + +QString QHttpNetworkReply::errorString() const +{ + return d_func()->errorString; +} + +QString QHttpNetworkReply::reasonPhrase() const +{ + return d_func()->reasonPhrase; +} + +void QHttpNetworkReply::setErrorString(const QString &error) +{ + Q_D(QHttpNetworkReply); + d->errorString = error; +} + +int QHttpNetworkReply::majorVersion() const +{ + return d_func()->majorVersion; +} + +int QHttpNetworkReply::minorVersion() const +{ + return d_func()->minorVersion; +} + +qint64 QHttpNetworkReply::bytesAvailable() const +{ + Q_D(const QHttpNetworkReply); + if (d->connection) + return d->connection->d_func()->bytesAvailable(*this); + else + return -1; +} + +QByteArray QHttpNetworkReply::read(qint64 maxSize) +{ + Q_D(QHttpNetworkReply); + QByteArray data; + if (d->connection) + d->connection->d_func()->read(*this, data, maxSize, false); + return data; +} + +bool QHttpNetworkReply::isFinished() const +{ + return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; +} + +// SSL support below +#ifndef QT_NO_OPENSSL +void QHttpNetworkConnectionPrivate::_q_encrypted() +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; // ### error + channels[indexOf(socket)].state = IdleState; + sendRequest(socket); +} + +void QHttpNetworkConnectionPrivate::_q_sslErrors(const QList<QSslError> &errors) +{ + Q_Q(QHttpNetworkConnection); + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(q->sender()); + if (!socket) + return; + //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure; + emit q->sslErrors(errors); +} + +QSslConfiguration QHttpNetworkConnectionPrivate::sslConfiguration(const QHttpNetworkReply &reply) const +{ + for (int i = 0; i < channelCount; ++i) + if (channels[i].reply == &reply) + return static_cast<QSslSocket *>(channels[0].socket)->sslConfiguration(); + return QSslConfiguration(); // pending or done request +} + +void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config) +{ + Q_D(QHttpNetworkConnection); + // set the config on all channels + for (int i = 0; i < d->channelCount; ++i) + static_cast<QSslSocket *>(d->channels[i].socket)->setSslConfiguration(config); +} + +void QHttpNetworkConnection::ignoreSslErrors(int channel) +{ + Q_D(QHttpNetworkConnection); + if (channel == -1) { // ignore for all channels + for (int i = 0; i < d->channelCount; ++i) { + static_cast<QSslSocket *>(d->channels[i].socket)->ignoreSslErrors(); + d->channels[i].ignoreSSLErrors = true; + } + + } else { + static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors(); + d->channels[channel].ignoreSSLErrors = true; + } +} + +QSslConfiguration QHttpNetworkReply::sslConfiguration() const +{ + Q_D(const QHttpNetworkReply); + if (d->connection) + return d->connection->d_func()->sslConfiguration(*this); + return QSslConfiguration(); +} + +void QHttpNetworkReply::setSslConfiguration(const QSslConfiguration &config) +{ + Q_D(QHttpNetworkReply); + if (d->connection) + d->connection->setSslConfiguration(config); +} + +void QHttpNetworkReply::ignoreSslErrors() +{ + Q_D(QHttpNetworkReply); + if (d->connection) + d->connection->ignoreSslErrors(); +} +#endif //QT_NO_OPENSSL + + +QT_END_NAMESPACE + +#include "moc_qhttpnetworkconnection_p.cpp" + +#endif // QT_NO_HTTP diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h new file mode 100644 index 0000000..8dbcb3d --- /dev/null +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPNETWORKCONNECTION_H +#define QHTTPNETWORKCONNECTION_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 <QtNetwork/qnetworkrequest.h> +#include <QtNetwork/qnetworkreply.h> +#include <QtNetwork/qabstractsocket.h> + +#ifndef QT_NO_HTTP + +#ifndef QT_NO_OPENSSL +# include <QtNetwork/qsslsocket.h> +# include <QtNetwork/qsslerror.h> +#else +# include <QtNetwork/qtcpsocket.h> +#endif + +QT_BEGIN_NAMESPACE + +class QHttpNetworkRequest; +class QHttpNetworkReply; + +class QHttpNetworkConnectionPrivate; +class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject +{ + Q_OBJECT +public: + + QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0); + ~QHttpNetworkConnection(); + + //The hostname to which this is connected to. + QString hostName() const; + //The HTTP port in use. + quint16 port() const; + + //add a new HTTP request through this connection + QHttpNetworkReply* sendRequest(const QHttpNetworkRequest &request); + +#ifndef QT_NO_NETWORKPROXY + //set the proxy for this connection + void setCacheProxy(const QNetworkProxy &networkProxy); + QNetworkProxy cacheProxy() const; + void setTransparentProxy(const QNetworkProxy &networkProxy); + QNetworkProxy transparentProxy() const; +#endif + + //enable encryption + void enableEncryption(); + bool isEncrypted() const; + + //authentication parameters + void setProxyAuthentication(QAuthenticator *authenticator); + void setAuthentication(const QString &domain, QAuthenticator *authenticator); + +#ifndef QT_NO_OPENSSL + void setSslConfiguration(const QSslConfiguration &config); + void ignoreSslErrors(int channel = -1); + +Q_SIGNALS: + void sslErrors(const QList<QSslError> &errors); +#endif + +Q_SIGNALS: +#ifndef QT_NO_NETWORKPROXY + //cannot be used with queued connection. + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator, + const QHttpNetworkConnection *connection = 0); +#endif + void authenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *authenticator, + const QHttpNetworkConnection *connection = 0); + void error(QNetworkReply::NetworkError errorCode, const QString &detail = QString()); + +private: + Q_DECLARE_PRIVATE(QHttpNetworkConnection) + Q_DISABLE_COPY(QHttpNetworkConnection) + friend class QHttpNetworkReply; + + Q_PRIVATE_SLOT(d_func(), void _q_bytesWritten(qint64)) + Q_PRIVATE_SLOT(d_func(), void _q_readyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_disconnected()) + Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest()) + Q_PRIVATE_SLOT(d_func(), void _q_restartPendingRequest()) + Q_PRIVATE_SLOT(d_func(), void _q_connected()) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) +#ifndef QT_NO_NETWORKPROXY + Q_PRIVATE_SLOT(d_func(), void _q_proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_dataReadyReadBuffer()) + Q_PRIVATE_SLOT(d_func(), void _q_dataReadyReadNoBuffer()) + +#ifndef QT_NO_OPENSSL + Q_PRIVATE_SLOT(d_func(), void _q_encrypted()) + Q_PRIVATE_SLOT(d_func(), void _q_sslErrors(const QList<QSslError>&)) +#endif +}; + +class Q_AUTOTEST_EXPORT QHttpNetworkHeader +{ +public: + virtual ~QHttpNetworkHeader() {}; + virtual QUrl url() const = 0; + virtual void setUrl(const QUrl &url) = 0; + + virtual int majorVersion() const = 0; + virtual int minorVersion() const = 0; + + virtual qint64 contentLength() const = 0; + virtual void setContentLength(qint64 length) = 0; + + virtual QList<QPair<QByteArray, QByteArray> > header() const = 0; + virtual QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const = 0; + virtual void setHeaderField(const QByteArray &name, const QByteArray &data) = 0; +}; + +class QHttpNetworkRequestPrivate; +class Q_AUTOTEST_EXPORT QHttpNetworkRequest: public QHttpNetworkHeader +{ +public: + enum Operation { + Options, + Get, + Head, + Post, + Put, + Delete, + Trace, + Connect + }; + + enum Priority { + HighPriority, + NormalPriority, + LowPriority + }; + + QHttpNetworkRequest(const QUrl &url = QUrl(), Operation operation = Get, Priority priority = NormalPriority); + QHttpNetworkRequest(const QHttpNetworkRequest &other); + virtual ~QHttpNetworkRequest(); + QHttpNetworkRequest &operator=(const QHttpNetworkRequest &other); + bool operator==(const QHttpNetworkRequest &other) const; + + QUrl url() const; + void setUrl(const QUrl &url); + + int majorVersion() const; + int minorVersion() const; + + qint64 contentLength() const; + void setContentLength(qint64 length); + + QList<QPair<QByteArray, QByteArray> > header() const; + QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const; + void setHeaderField(const QByteArray &name, const QByteArray &data); + + Operation operation() const; + void setOperation(Operation operation); + + Priority priority() const; + void setPriority(Priority priority); + + QIODevice *data() const; + void setData(QIODevice *data); + +private: + QSharedDataPointer<QHttpNetworkRequestPrivate> d; + friend class QHttpNetworkRequestPrivate; + friend class QHttpNetworkConnectionPrivate; +}; + +class QHttpNetworkReplyPrivate; +class Q_AUTOTEST_EXPORT QHttpNetworkReply : public QObject, public QHttpNetworkHeader +{ + Q_OBJECT +public: + + explicit QHttpNetworkReply(const QUrl &url = QUrl(), QObject *parent = 0); + virtual ~QHttpNetworkReply(); + + QUrl url() const; + void setUrl(const QUrl &url); + + int majorVersion() const; + int minorVersion() const; + + qint64 contentLength() const; + void setContentLength(qint64 length); + + QList<QPair<QByteArray, QByteArray> > header() const; + QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const; + void setHeaderField(const QByteArray &name, const QByteArray &data); + void parseHeader(const QByteArray &header); // mainly for testing + + QHttpNetworkRequest request() const; + void setRequest(const QHttpNetworkRequest &request); + + int statusCode() const; + void setStatusCode(int code); + + QString errorString() const; + void setErrorString(const QString &error); + + QString reasonPhrase() const; + + qint64 bytesAvailable() const; + QByteArray read(qint64 maxSize = -1); + + bool isFinished() const; + +#ifndef QT_NO_OPENSSL + QSslConfiguration sslConfiguration() const; + void setSslConfiguration(const QSslConfiguration &config); + void ignoreSslErrors(); + +Q_SIGNALS: + void sslErrors(const QList<QSslError> &errors); +#endif + +Q_SIGNALS: + void readyRead(); + void finished(); + void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail = QString()); + void headerChanged(); + void dataReadProgress(int done, int total); + void dataSendProgress(int done, int total); + +private: + Q_DECLARE_PRIVATE(QHttpNetworkReply) + friend class QHttpNetworkConnection; + friend class QHttpNetworkConnectionPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QHttpNetworkRequest) +//Q_DECLARE_METATYPE(QHttpNetworkReply) + +#endif // QT_NO_HTTP + +#endif diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp new file mode 100644 index 0000000..df468b8 --- /dev/null +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessbackend_p.h" +#include "qnetworkaccessmanager_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include "QtCore/qhash.h" +#include "QtCore/qmutex.h" + +#include "qnetworkaccesscachebackend_p.h" +#include "qabstractnetworkcache.h" + +QT_BEGIN_NAMESPACE + +static bool factoryDataShutdown = false; +class QNetworkAccessBackendFactoryData: public QList<QNetworkAccessBackendFactory *> +{ +public: + QNetworkAccessBackendFactoryData() : mutex(QMutex::Recursive) { } + ~QNetworkAccessBackendFactoryData() + { + QMutexLocker locker(&mutex); // why do we need to lock? + factoryDataShutdown = true; + } + + QMutex mutex; +}; +Q_GLOBAL_STATIC(QNetworkAccessBackendFactoryData, factoryData) + +QNetworkAccessBackendFactory::QNetworkAccessBackendFactory() +{ + QMutexLocker locker(&factoryData()->mutex); + factoryData()->prepend(this); +} + +QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory() +{ + if (!factoryDataShutdown) { + QMutexLocker locker(&factoryData()->mutex); + factoryData()->removeAll(this); + } +} + +QNetworkAccessBackend *QNetworkAccessManagerPrivate::findBackend(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) +{ + QNetworkRequest::CacheLoadControl mode = + static_cast<QNetworkRequest::CacheLoadControl>( + request.attribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::PreferNetwork).toInt()); + if (mode == QNetworkRequest::AlwaysCache + && (op == QNetworkAccessManager::GetOperation + || op == QNetworkAccessManager::HeadOperation)) + return new QNetworkAccessCacheBackend; + + if (!factoryDataShutdown) { + QMutexLocker locker(&factoryData()->mutex); + QNetworkAccessBackendFactoryData::ConstIterator it = factoryData()->constBegin(), + end = factoryData()->constEnd(); + while (it != end) { + QNetworkAccessBackend *backend = (*it)->create(op, request); + if (backend) { + backend->manager = this; + return backend; // found a factory that handled our request + } + ++it; + } + } + return 0; +} + +QNetworkAccessBackend::QNetworkAccessBackend() +{ +} + +QNetworkAccessBackend::~QNetworkAccessBackend() +{ +} + +void QNetworkAccessBackend::upstreamReadyRead() +{ + // do nothing +} + +void QNetworkAccessBackend::downstreamReadyWrite() +{ + // do nothing +} + +void QNetworkAccessBackend::copyFinished(QIODevice *) +{ + // do nothing +} + +void QNetworkAccessBackend::ignoreSslErrors() +{ + // do nothing +} + +void QNetworkAccessBackend::fetchSslConfiguration(QSslConfiguration &) const +{ + // do nothing +} + +void QNetworkAccessBackend::setSslConfiguration(const QSslConfiguration &) +{ + // do nothing +} + +QNetworkCacheMetaData QNetworkAccessBackend::fetchCacheMetaData(const QNetworkCacheMetaData &) const +{ + return QNetworkCacheMetaData(); +} + +QNetworkAccessManager::Operation QNetworkAccessBackend::operation() const +{ + return reply->operation; +} + +QNetworkRequest QNetworkAccessBackend::request() const +{ + return reply->request; +} + +#ifndef QT_NO_NETWORKPROXY +QList<QNetworkProxy> QNetworkAccessBackend::proxyList() const +{ + return reply->proxyList; +} +#endif + +QAbstractNetworkCache *QNetworkAccessBackend::networkCache() const +{ + return reply->networkCache; // should be the same as manager->networkCache +} + +void QNetworkAccessBackend::setCachingEnabled(bool enable) +{ + reply->setCachingEnabled(enable); +} + +bool QNetworkAccessBackend::isCachingEnabled() const +{ + return reply->isCachingEnabled(); +} + +qint64 QNetworkAccessBackend::upstreamBytesAvailable() const +{ + return reply->writeBuffer.size(); +} + +void QNetworkAccessBackend::upstreamBytesConsumed(qint64 count) +{ + // remove count bytes from the write buffer + reply->consume(count); +} + +QByteArray QNetworkAccessBackend::readUpstream() +{ + // ### this is expensive. Consider making QRingBuffer::peekAll keep the buffer it returns + return reply->writeBuffer.peek(upstreamBytesAvailable()); +} + +qint64 QNetworkAccessBackend::nextDownstreamBlockSize() const +{ + return reply->nextDownstreamBlockSize(); +} + +qint64 QNetworkAccessBackend::downstreamBytesToConsume() const +{ + return reply->writeBuffer.size(); +} + +void QNetworkAccessBackend::writeDownstreamData(const QByteArray &data) +{ + reply->feed(data); +} + +void QNetworkAccessBackend::writeDownstreamData(QIODevice *data) +{ + reply->feed(data); +} + +QVariant QNetworkAccessBackend::header(QNetworkRequest::KnownHeaders header) const +{ + return reply->q_func()->header(header); +} + +void QNetworkAccessBackend::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) +{ + reply->setCookedHeader(header, value); +} + +bool QNetworkAccessBackend::hasRawHeader(const QByteArray &headerName) const +{ + return reply->q_func()->hasRawHeader(headerName); +} + +QByteArray QNetworkAccessBackend::rawHeader(const QByteArray &headerName) const +{ + return reply->q_func()->rawHeader(headerName); +} + +QList<QByteArray> QNetworkAccessBackend::rawHeaderList() const +{ + return reply->q_func()->rawHeaderList(); +} + +void QNetworkAccessBackend::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue) +{ + reply->setRawHeader(headerName, headerValue); +} + +QVariant QNetworkAccessBackend::attribute(QNetworkRequest::Attribute code) const +{ + return reply->q_func()->attribute(code); +} + +void QNetworkAccessBackend::setAttribute(QNetworkRequest::Attribute code, const QVariant &value) +{ + if (value.isValid()) + reply->attributes.insert(code, value); + else + reply->attributes.remove(code); +} +QUrl QNetworkAccessBackend::url() const +{ + return reply->url; +} + +void QNetworkAccessBackend::setUrl(const QUrl &url) +{ + reply->url = url; +} + +void QNetworkAccessBackend::finished() +{ + reply->finished(); +} + +void QNetworkAccessBackend::error(QNetworkReply::NetworkError code, const QString &errorString) +{ + reply->error(code, errorString); +} + +#ifndef QT_NO_NETWORKPROXY +void QNetworkAccessBackend::proxyAuthenticationRequired(const QNetworkProxy &proxy, + QAuthenticator *authenticator) +{ + manager->proxyAuthenticationRequired(this, proxy, authenticator); +} +#endif + +void QNetworkAccessBackend::authenticationRequired(QAuthenticator *authenticator) +{ + manager->authenticationRequired(this, authenticator); +} + +void QNetworkAccessBackend::metaDataChanged() +{ + reply->metaDataChanged(); +} + +void QNetworkAccessBackend::redirectionRequested(const QUrl &target) +{ + reply->redirectionRequested(target); +} + +void QNetworkAccessBackend::sslErrors(const QList<QSslError> &errors) +{ +#ifndef QT_NO_OPENSSL + reply->sslErrors(errors); +#else + Q_UNUSED(errors); +#endif +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h new file mode 100644 index 0000000..9012396 --- /dev/null +++ b/src/network/access/qnetworkaccessbackend_p.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSBACKEND_P_H +#define QNETWORKACCESSBACKEND_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 "qnetworkreplyimpl_p.h" +#include "QtCore/qobject.h" + +QT_BEGIN_NAMESPACE + +class QAuthenticator; +class QNetworkProxy; +class QNetworkProxyQuery; +class QNetworkRequest; +class QUrl; +class QUrlInfo; +class QSslConfiguration; + +class QNetworkAccessManagerPrivate; +class QNetworkReplyImplPrivate; +class QAbstractNetworkCache; +class QNetworkCacheMetaData; + +// Should support direct file upload from disk or download to disk. +// +// - The HTTP handler will use two QIODevices for communication (pull mechanism) +// - KIO uses a pull mechanism too (data/dataReq signals) +class QNetworkAccessBackend : public QObject +{ + Q_OBJECT +public: + QNetworkAccessBackend(); + virtual ~QNetworkAccessBackend(); + + // To avoid mistaking with QIODevice names, the functions here + // have different names. The Connection has two streams: + // + // - Upstream: + // Upstream is data that is being written into this connection, + // from the user. Upstream operates in a "pull" mechanism: the + // connection will be notified that there is more data available + // by a call to "upstreamReadyRead". The number of bytes + // available is given by upstreamBytesAvailable(). A call to + // readUpstream() always yields the entire upstream buffer. When + // the connection has processed a certain amount of bytes from + // that buffer, it should call upstreamBytesConsumed(). + // + // - Downstream: + // Downstream is the data that is being read from this + // connection and is given to the user. Downstream operates in a + // semi-"push" mechanism: the Connection object pushes the data + // it gets from the network, but it should avoid writing too + // much if the data isn't being used fast enough. The value + // returned by suggestedDownstreamBlockSize() can be used to + // determine how much should be written at a time. The + // downstreamBytesConsumed() function will be called when the + // downstream buffer is consumed by the user -- the Connection + // may choose to re-fill it with more data it has ready or get + // more data from the network (for instance, by reading from its + // socket). + + virtual void open() = 0; + virtual void closeDownstreamChannel() = 0; + virtual void closeUpstreamChannel() = 0; + virtual bool waitForDownstreamReadyRead(int msecs) = 0; + virtual bool waitForUpstreamBytesWritten(int msecs) = 0; + + // slot-like: + virtual void upstreamReadyRead(); + virtual void downstreamReadyWrite(); + virtual void copyFinished(QIODevice *); + virtual void ignoreSslErrors(); + + virtual void fetchSslConfiguration(QSslConfiguration &configuration) const; + virtual void setSslConfiguration(const QSslConfiguration &configuration); + + virtual QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const; + + // information about the request + QNetworkAccessManager::Operation operation() const; + QNetworkRequest request() const; +#ifndef QT_NO_NETWORKPROXY + QList<QNetworkProxy> proxyList() const; +#endif + + QAbstractNetworkCache *networkCache() const; + void setCachingEnabled(bool enable); + bool isCachingEnabled() const; + + // information about the reply + QUrl url() const; + void setUrl(const QUrl &url); + + // "cooked" headers + QVariant header(QNetworkRequest::KnownHeaders header) const; + void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); + + // raw headers: + bool hasRawHeader(const QByteArray &headerName) const; + QList<QByteArray> rawHeaderList() const; + QByteArray rawHeader(const QByteArray &headerName) const; + void setRawHeader(const QByteArray &headerName, const QByteArray &value); + + // attributes: + QVariant attribute(QNetworkRequest::Attribute code) const; + void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); + +protected: + // these functions control the upstream mechanism + // that is, data coming into the backend and out via the connection + qint64 upstreamBytesAvailable() const; + void upstreamBytesConsumed(qint64 count); + QByteArray readUpstream(); + + // these functions control the downstream mechanism + // that is, data that has come via the connection and is going out the backend + qint64 nextDownstreamBlockSize() const; + qint64 downstreamBytesToConsume() const; + void writeDownstreamData(const QByteArray &data); + void writeDownstreamData(QIODevice *data); + +protected slots: + void finished(); + void error(QNetworkReply::NetworkError code, const QString &errorString); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth); +#endif + void authenticationRequired(QAuthenticator *auth); + void metaDataChanged(); + void redirectionRequested(const QUrl &destination); + void sslErrors(const QList<QSslError> &errors); + +private: + friend class QNetworkAccessManager; + friend class QNetworkAccessManagerPrivate; + QNetworkAccessManagerPrivate *manager; + QNetworkReplyImplPrivate *reply; +}; + +class QNetworkAccessBackendFactory +{ +public: + QNetworkAccessBackendFactory(); + virtual ~QNetworkAccessBackendFactory(); + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const = 0; +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/network/access/qnetworkaccesscache.cpp b/src/network/access/qnetworkaccesscache.cpp new file mode 100644 index 0000000..9b2be3d --- /dev/null +++ b/src/network/access/qnetworkaccesscache.cpp @@ -0,0 +1,379 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccesscache_p.h" +#include "QtCore/qpointer.h" +#include "QtCore/qdatetime.h" +#include "QtCore/qqueue.h" +#include "qnetworkaccessmanager_p.h" +#include "qnetworkreply_p.h" +#include "qnetworkrequest.h" + +QT_BEGIN_NAMESPACE + +enum ExpiryTimeEnum { + ExpiryTime = 120 +}; + +namespace { + struct Receiver + { + QPointer<QObject> object; + const char *member; + }; +} + +// idea copied from qcache.h +struct QNetworkAccessCache::Node +{ + QDateTime timestamp; + QQueue<Receiver> receiverQueue; + QByteArray key; + + Node *older, *newer; + CacheableObject *object; + + int useCount; + + Node() + : older(0), newer(0), object(0), useCount(0) + { } +}; + +QNetworkAccessCache::CacheableObject::CacheableObject() +{ + // leave the members uninitialized + // they must be initialized by the derived class's constructor +} + +QNetworkAccessCache::CacheableObject::~CacheableObject() +{ +#if 0 //def QT_DEBUG + if (!key.isEmpty() && Ptr()->hasEntry(key)) + qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key + << "destroyed without being removed from cache first!"; +#endif +} + +void QNetworkAccessCache::CacheableObject::setExpires(bool enable) +{ + expires = enable; +} + +void QNetworkAccessCache::CacheableObject::setShareable(bool enable) +{ + shareable = enable; +} + +QNetworkAccessCache::QNetworkAccessCache() + : oldest(0), newest(0) +{ +} + +QNetworkAccessCache::~QNetworkAccessCache() +{ + clear(); +} + +void QNetworkAccessCache::clear() +{ + NodeHash hashCopy = hash; + hash.clear(); + + // remove all entries + NodeHash::Iterator it = hashCopy.begin(); + NodeHash::Iterator end = hashCopy.end(); + for ( ; it != end; ++it) { + it->object->key.clear(); + it->object->dispose(); + } + + // now delete: + hashCopy.clear(); + + timer.stop(); + + oldest = newest = 0; +} + +/*! + Appens the entry given by @p key to the end of the linked list. + (i.e., makes it the newest entry) + */ +void QNetworkAccessCache::linkEntry(const QByteArray &key) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) + return; + + Node *const node = &it.value(); + Q_ASSERT(node != oldest && node != newest); + Q_ASSERT(node->older == 0 && node->newer == 0); + Q_ASSERT(node->useCount == 0); + + if (newest) { + Q_ASSERT(newest->newer == 0); + newest->newer = node; + node->older = newest; + } + if (!oldest) { + // there are no entries, so this is the oldest one too + oldest = node; + } + + node->timestamp = QDateTime::currentDateTime().addSecs(ExpiryTime); + newest = node; +} + +/*! + Removes the entry pointed by @p key from the linked list. + Returns true if the entry removed was the oldest one. + */ +bool QNetworkAccessCache::unlinkEntry(const QByteArray &key) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) + return false; + + Node *const node = &it.value(); + + bool wasOldest = false; + if (node == oldest) { + oldest = node->newer; + wasOldest = true; + } + if (node == newest) + newest = node->older; + if (node->older) + node->older->newer = node->newer; + if (node->newer) + node->newer->older = node->older; + + node->newer = node->older = 0; + return wasOldest; +} + +void QNetworkAccessCache::updateTimer() +{ + timer.stop(); + + if (!oldest) + return; + + int interval = QDateTime::currentDateTime().secsTo(oldest->timestamp); + if (interval <= 0) { + interval = 0; + } else { + // round up the interval + interval = (interval + 15) & ~16; + } + + timer.start(interval * 1000, this); +} + +bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member) +{ + if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)), + target, member, Qt::QueuedConnection)) + return false; + + emit entryReady(node->object); + disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*))); + + return true; +} + +void QNetworkAccessCache::timerEvent(QTimerEvent *) +{ + // expire old items + QDateTime now = QDateTime::currentDateTime(); + + while (oldest && oldest->timestamp < now) { + Node *next = oldest->newer; + oldest->object->dispose(); + + hash.remove(oldest->key); // oldest gets deleted + oldest = next; + } + + // fixup the list + if (oldest) + oldest->older = 0; + else + newest = 0; + + updateTimer(); +} + +void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry) +{ + Q_ASSERT(!key.isEmpty()); + + if (unlinkEntry(key)) + updateTimer(); + + Node &node = hash[key]; // create the entry in the hash if it didn't exist + if (node.useCount) + qWarning("QNetworkAccessCache::addEntry: overriding active cache entry '%s'", + key.constData()); + if (node.object) + node.object->dispose(); + node.object = entry; + node.object->key = key; + node.key = key; + node.useCount = 1; +} + +bool QNetworkAccessCache::hasEntry(const QByteArray &key) const +{ + return hash.contains(key); +} + +bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) + return false; // no such entry + + Node *node = &it.value(); + + if (node->useCount > 0 && !node->object->shareable) { + // object is not shareable and is in use + // queue for later use + Q_ASSERT(node->older == 0 && node->newer == 0); + Receiver receiver; + receiver.object = target; + receiver.member = member; + node->receiverQueue.enqueue(receiver); + + // request queued + return true; + } else { + // node not in use or is shareable + if (unlinkEntry(key)) + updateTimer(); + + ++node->useCount; + return emitEntryReady(node, target, member); + } +} + +QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) + return 0; + if (it->useCount > 0) { + if (it->object->shareable) { + ++it->useCount; + return it->object; + } + + // object in use and not shareable + return 0; + } + + // entry not in use, let the caller have it + bool wasOldest = unlinkEntry(key); + ++it->useCount; + + if (wasOldest) + updateTimer(); + return it->object; +} + +void QNetworkAccessCache::releaseEntry(const QByteArray &key) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) { + qWarning("QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache", + key.constData()); + return; + } + + Node *node = &it.value(); + Q_ASSERT(node->useCount > 0); + + // are there other objects waiting? + if (!node->receiverQueue.isEmpty()) { + // queue another activation + Receiver receiver; + do { + receiver = node->receiverQueue.dequeue(); + } while (receiver.object.isNull() && !node->receiverQueue.isEmpty()); + + if (!receiver.object.isNull()) { + emitEntryReady(node, receiver.object, receiver.member); + return; + } + } + + if (!--node->useCount) { + // no objects waiting; add it back to the expiry list + if (node->object->expires) + linkEntry(key); + + if (oldest == node) + updateTimer(); + } +} + +void QNetworkAccessCache::removeEntry(const QByteArray &key) +{ + NodeHash::Iterator it = hash.find(key); + if (it == hash.end()) { + qWarning("QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache", + key.constData()); + return; + } + + Node *node = &it.value(); + if (unlinkEntry(key)) + updateTimer(); + if (node->useCount > 1) + qWarning("QNetworkAccessCache::removeEntry: removing active cache entry '%s'", + key.constData()); + + node->object->key.clear(); + hash.remove(node->key); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccesscache_p.h b/src/network/access/qnetworkaccesscache_p.h new file mode 100644 index 0000000..23d8828 --- /dev/null +++ b/src/network/access/qnetworkaccesscache_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSCACHE_P_H +#define QNETWORKACCESSCACHE_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/qobject.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qhash.h" +#include "QtCore/qmetatype.h" + +QT_BEGIN_NAMESPACE + +class QNetworkRequest; +class QUrl; + +class QNetworkAccessCache: public QObject +{ + Q_OBJECT +public: + struct Node; + typedef QHash<QByteArray, Node> NodeHash; + + class CacheableObject + { + friend class QNetworkAccessCache; + QByteArray key; + bool expires; + bool shareable; + public: + CacheableObject(); + virtual ~CacheableObject(); + virtual void dispose() = 0; + inline QByteArray cacheKey() const { return key; } + + protected: + void setExpires(bool enable); + void setShareable(bool enable); + }; + + QNetworkAccessCache(); + ~QNetworkAccessCache(); + + void clear(); + + void addEntry(const QByteArray &key, CacheableObject *entry); + bool hasEntry(const QByteArray &key) const; + bool requestEntry(const QByteArray &key, QObject *target, const char *member); + CacheableObject *requestEntryNow(const QByteArray &key); + void releaseEntry(const QByteArray &key); + void removeEntry(const QByteArray &key); + +signals: + void entryReady(QNetworkAccessCache::CacheableObject *); + +protected: + void timerEvent(QTimerEvent *); + +private: + // idea copied from qcache.h + NodeHash hash; + Node *oldest; + Node *newest; + + QBasicTimer timer; + + void linkEntry(const QByteArray &key); + bool unlinkEntry(const QByteArray &key); + void updateTimer(); + bool emitEntryReady(Node *node, QObject *target, const char *member); +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QNetworkAccessCache::CacheableObject*) + +#endif diff --git a/src/network/access/qnetworkaccesscachebackend.cpp b/src/network/access/qnetworkaccesscachebackend.cpp new file mode 100644 index 0000000..fcb294f --- /dev/null +++ b/src/network/access/qnetworkaccesscachebackend.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNETWORKACCESSCACHEBACKEND_DEBUG + +#include "qnetworkaccesscachebackend_p.h" +#include "qabstractnetworkcache.h" +#include "qfileinfo.h" +#include "qurlinfo.h" +#include "qdir.h" +#include "qcoreapplication.h" + +QT_BEGIN_NAMESPACE + +QNetworkAccessCacheBackend::QNetworkAccessCacheBackend() + : QNetworkAccessBackend() +{ +} + +QNetworkAccessCacheBackend::~QNetworkAccessCacheBackend() +{ +} + +void QNetworkAccessCacheBackend::open() +{ + if (operation() != QNetworkAccessManager::GetOperation || !sendCacheContents()) { + QString msg = QCoreApplication::translate("QNetworkAccessCacheBackend", "Error opening %1") + .arg(this->url().toString()); + error(QNetworkReply::ContentNotFoundError, msg); + setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); + } + finished(); +} + +bool QNetworkAccessCacheBackend::sendCacheContents() +{ + setCachingEnabled(false); + QAbstractNetworkCache *nc = networkCache(); + if (!nc) + return false; + + QNetworkCacheMetaData item = nc->metaData(url()); + if (!item.isValid()) + return false; + + QNetworkCacheMetaData::AttributesMap attributes = item.attributes(); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, attributes.value(QNetworkRequest::HttpStatusCodeAttribute)); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); + setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); + + // signal we're open + metaDataChanged(); + + if (operation() == QNetworkAccessManager::GetOperation) { + QIODevice *contents = nc->data(url()); + if (!contents) + return false; + contents->setParent(this); + writeDownstreamData(contents); + } + +#if defined(QNETWORKACCESSCACHEBACKEND_DEBUG) + qDebug() << "Successfully sent cache:" << url(); +#endif + return true; +} + +void QNetworkAccessCacheBackend::closeDownstreamChannel() +{ + if (operation() == QNetworkAccessManager::GetOperation) { + device->close(); + delete device; + device = 0; + } +} + +void QNetworkAccessCacheBackend::closeUpstreamChannel() +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); +} + +bool QNetworkAccessCacheBackend::waitForDownstreamReadyRead(int) +{ + Q_ASSERT_X(false, Q_FUNC_INFO , "This function show not have been called!"); + return false; +} + +bool QNetworkAccessCacheBackend::waitForUpstreamBytesWritten(int) +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); + return false; +} + +void QNetworkAccessCacheBackend::upstreamReadyRead() +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); +} + +void QNetworkAccessCacheBackend::downstreamReadyWrite() +{ + Q_ASSERT_X(false, Q_FUNC_INFO, "This function show not have been called!"); +} + +QT_END_NAMESPACE + diff --git a/src/network/access/qnetworkaccesscachebackend_p.h b/src/network/access/qnetworkaccesscachebackend_p.h new file mode 100644 index 0000000..0d864f5 --- /dev/null +++ b/src/network/access/qnetworkaccesscachebackend_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSCACHEBACKEND_P_H +#define QNETWORKACCESSCACHEBACKEND_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 "qnetworkaccessbackend_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" + +QT_BEGIN_NAMESPACE + +class QNetworkAccessCacheBackend : public QNetworkAccessBackend +{ + +public: + QNetworkAccessCacheBackend(); + ~QNetworkAccessCacheBackend(); + + void open(); + void closeDownstreamChannel(); + void closeUpstreamChannel(); + bool waitForDownstreamReadyRead(int msecs); + bool waitForUpstreamBytesWritten(int msecs); + + void upstreamReadyRead(); + void downstreamReadyWrite(); + +private: + bool sendCacheContents(); + QIODevice *device; + +}; + +QT_END_NAMESPACE + +#endif // QNETWORKACCESSCACHEBACKEND_P_H diff --git a/src/network/access/qnetworkaccessdatabackend.cpp b/src/network/access/qnetworkaccessdatabackend.cpp new file mode 100644 index 0000000..f31247c --- /dev/null +++ b/src/network/access/qnetworkaccessdatabackend.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessdatabackend_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qurlinfo.h" + +QT_BEGIN_NAMESPACE + +QNetworkAccessBackend * +QNetworkAccessDataBackendFactory::create(QNetworkAccessManager::Operation, + const QNetworkRequest &request) const +{ + if (request.url().scheme() == QLatin1String("data")) + return new QNetworkAccessDataBackend; + + return 0; +} + +QNetworkAccessDataBackend::QNetworkAccessDataBackend() +{ +} + +QNetworkAccessDataBackend::~QNetworkAccessDataBackend() +{ +} + +void QNetworkAccessDataBackend::open() +{ + QUrl uri = request().url(); + + if (operation() != QNetworkAccessManager::GetOperation && + operation() != QNetworkAccessManager::HeadOperation) { + // data: doesn't support anything but GET + QString msg = QObject::tr("Operation not supported on %1") + .arg(uri.toString()); + error(QNetworkReply::ContentOperationNotPermittedError, msg); + finished(); + return; + } + + if (uri.host().isEmpty()) { + setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("text/plain;charset=US-ASCII")); + + // the following would have been the correct thing, but + // reality often differs from the specification. People have + // data: URIs with ? and # + //QByteArray data = QByteArray::fromPercentEncoding(uri.encodedPath()); + QByteArray data = QByteArray::fromPercentEncoding(uri.toEncoded()); + + // remove the data: scheme + data.remove(0, 5); + + // parse it: + int pos = data.indexOf(','); + if (pos != -1) { + QByteArray payload = data.mid(pos + 1); + data.truncate(pos); + data = data.trimmed(); + + // find out if the payload is encoded in Base64 + if (data.endsWith(";base64")) { + payload = QByteArray::fromBase64(payload); + data.chop(7); + } + + if (data.toLower().startsWith("charset")) { + int i = 7; // strlen("charset") + while (data.at(i) == ' ') + ++i; + if (data.at(i) == '=') + data.prepend("text/plain;"); + } + + if (!data.isEmpty()) + setHeader(QNetworkRequest::ContentTypeHeader, data.trimmed()); + + setHeader(QNetworkRequest::ContentLengthHeader, payload.size()); + emit metaDataChanged(); + + writeDownstreamData(payload); + finished(); + return; + } + } + + // something wrong with this URI + QString msg = QObject::tr("Invalid URI: %1").arg(uri.toString()); + error(QNetworkReply::ProtocolFailure, msg); + finished(); +} + +void QNetworkAccessDataBackend::closeDownstreamChannel() +{ +} + +void QNetworkAccessDataBackend::closeUpstreamChannel() +{ +} + +bool QNetworkAccessDataBackend::waitForDownstreamReadyRead(int) +{ + return false; +} + +bool QNetworkAccessDataBackend::waitForUpstreamBytesWritten(int) +{ + return false; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessdatabackend_p.h b/src/network/access/qnetworkaccessdatabackend_p.h new file mode 100644 index 0000000..54eca3d --- /dev/null +++ b/src/network/access/qnetworkaccessdatabackend_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSDATABACKEND_P_H +#define QNETWORKACCESSDATABACKEND_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 "qnetworkaccessbackend_p.h" + +QT_BEGIN_NAMESPACE + +class QNetworkAccessDataBackend: public QNetworkAccessBackend +{ +public: + QNetworkAccessDataBackend(); + virtual ~QNetworkAccessDataBackend(); + + virtual void open(); + virtual void closeDownstreamChannel(); + virtual void closeUpstreamChannel(); + virtual bool waitForDownstreamReadyRead(int msecs); + virtual bool waitForUpstreamBytesWritten(int msecs); +}; + +class QNetworkAccessDataBackendFactory: public QNetworkAccessBackendFactory +{ +public: + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp new file mode 100644 index 0000000..2e5f1b1 --- /dev/null +++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessdebugpipebackend_p.h" +#include "QtCore/qdatastream.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_BUILD_INTERNAL + +enum { + ReadBufferSize = 16384, + WriteBufferSize = ReadBufferSize +}; + +struct QNetworkAccessDebugPipeBackend::DataPacket +{ + QList<QPair<QByteArray, QByteArray> > headers; + QByteArray data; +}; + +QNetworkAccessBackend * +QNetworkAccessDebugPipeBackendFactory::create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const +{ + // is it an operation we know of? + switch (op) { + case QNetworkAccessManager::GetOperation: + case QNetworkAccessManager::PutOperation: + break; + + default: + // no, we can't handle this operation + return 0; + } + + QUrl url = request.url(); + if (url.scheme() == QLatin1String("debugpipe")) + return new QNetworkAccessDebugPipeBackend; + return 0; +} + +QNetworkAccessDebugPipeBackend::QNetworkAccessDebugPipeBackend() + : incomingPacketSize(0), bareProtocol(false) +{ +} + +QNetworkAccessDebugPipeBackend::~QNetworkAccessDebugPipeBackend() +{ + socket.disconnect(this); // we're not interested in the signals at this point +} + +void QNetworkAccessDebugPipeBackend::open() +{ + socket.connectToHost(url().host(), url().port(12345)); + socket.setReadBufferSize(ReadBufferSize); + connect(&socket, SIGNAL(readyRead()), SLOT(socketReadyRead())); + connect(&socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64))); + connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError())); + connect(&socket, SIGNAL(disconnected()), SLOT(socketDisconnected())); + + bareProtocol = url().queryItemValue(QLatin1String("bare")) == QLatin1String("1"); + + if (!bareProtocol) { + // "Handshake": + // send outgoing metadata and the URL being requested + DataPacket packet; + //packet.metaData = request().metaData(); + packet.data = url().toEncoded(); + send(packet); + } +} + +void QNetworkAccessDebugPipeBackend::closeDownstreamChannel() +{ + if (operation() == QNetworkAccessManager::GetOperation) + socket.disconnectFromHost(); +} + +void QNetworkAccessDebugPipeBackend::closeUpstreamChannel() +{ + if (operation() == QNetworkAccessManager::PutOperation) + socket.disconnectFromHost(); + else if (operation() == QNetworkAccessManager::PostOperation) { + send(DataPacket()); + } +} + +bool QNetworkAccessDebugPipeBackend::waitForDownstreamReadyRead(int ms) +{ + readyReadEmitted = false; + if (socket.bytesAvailable()) { + socketReadyRead(); + if (readyReadEmitted) + return true; + } + socket.waitForReadyRead(ms); + return readyReadEmitted; +} + +bool QNetworkAccessDebugPipeBackend::waitForUpstreamBytesWritten(int ms) +{ + bytesWrittenEmitted = false; + upstreamReadyRead(); + if (bytesWrittenEmitted) + return true; + + socket.waitForBytesWritten(ms); + return bytesWrittenEmitted; +} + +void QNetworkAccessDebugPipeBackend::upstreamReadyRead() +{ + int maxWrite = WriteBufferSize - socket.bytesToWrite(); + if (maxWrite <= 0) + return; // can't write yet, wait for the socket to write + + if (bareProtocol) { + QByteArray data = readUpstream(); + if (data.isEmpty()) + return; + + socket.write(data); + upstreamBytesConsumed(data.size()); + bytesWrittenEmitted = true; + return; + } + + DataPacket packet; + packet.data = readUpstream(); + if (packet.data.isEmpty()) + return; // we'll be called again when there's data + if (packet.data.size() > maxWrite) + packet.data.truncate(maxWrite); + + if (!send(packet)) { + QString msg = QObject::tr("Write error writing to %1: %2") + .arg(url().toString(), socket.errorString()); + error(QNetworkReply::ProtocolFailure, msg); + + finished(); + return; + } + upstreamBytesConsumed(packet.data.size()); + bytesWrittenEmitted = true; +} + +void QNetworkAccessDebugPipeBackend::downstreamReadyWrite() +{ + socketReadyRead(); +} + +void QNetworkAccessDebugPipeBackend::socketReadyRead() +{ + if (bareProtocol) { + qint64 bytesToRead = socket.bytesAvailable(); + if (bytesToRead) { + QByteArray buffer; + buffer.resize(bytesToRead); + qint64 bytesRead = socket.read(buffer.data(), bytesToRead); + if (bytesRead < bytesToRead) + buffer.truncate(bytesRead); + writeDownstreamData(buffer); + readyReadEmitted = true; + } + return; + } + + while (canReceive() && + (socket.state() == QAbstractSocket::UnconnectedState || nextDownstreamBlockSize())) { + DataPacket packet; + if (receive(packet)) { + if (!packet.headers.isEmpty()) { + QList<QPair<QByteArray, QByteArray> >::ConstIterator + it = packet.headers.constBegin(), + end = packet.headers.constEnd(); + for ( ; it != end; ++it) + setRawHeader(it->first, it->second); + metaDataChanged(); + } + + if (!packet.data.isEmpty()) { + writeDownstreamData(packet.data); + readyReadEmitted = true; + } + + if (packet.headers.isEmpty() && packet.data.isEmpty()) { + // it's an eof + socket.close(); + readyReadEmitted = true; + } + } else { + // got an error + QString msg = QObject::tr("Read error reading from %1: %2") + .arg(url().toString(), socket.errorString()); + error(QNetworkReply::ProtocolFailure, msg); + + finished(); + return; + } + } +} + +void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64) +{ + upstreamReadyRead(); +} + +void QNetworkAccessDebugPipeBackend::socketError() +{ + QNetworkReply::NetworkError code; + switch (socket.error()) { + case QAbstractSocket::RemoteHostClosedError: + return; // socketDisconnected will be called + + case QAbstractSocket::NetworkError: + code = QNetworkReply::UnknownNetworkError; + break; + + default: + code = QNetworkReply::ProtocolFailure; + break; + } + + error(code, QObject::tr("Socket error on %1: %2") + .arg(url().toString(), socket.errorString())); + finished(); + disconnect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + +} + +void QNetworkAccessDebugPipeBackend::socketDisconnected() +{ + socketReadyRead(); + if (incomingPacketSize == 0 && socket.bytesToWrite() == 0) { + // normal close + finished(); + } else { + // abnormal close + QString msg = QObject::tr("Remote host closed the connection prematurely on %1") + .arg(url().toString()); + error(QNetworkReply::RemoteHostClosedError, msg); + + finished(); + } +} + +bool QNetworkAccessDebugPipeBackend::send(const DataPacket &packet) +{ + QByteArray ba; + { + QDataStream stream(&ba, QIODevice::WriteOnly); + stream.setVersion(QDataStream::Qt_4_4); + + stream << packet.headers << packet.data; + } + + qint32 outgoingPacketSize = ba.size(); + qint64 written = socket.write((const char*)&outgoingPacketSize, sizeof outgoingPacketSize); + written += socket.write(ba); + return quint64(written) == (outgoingPacketSize + sizeof outgoingPacketSize); +} + +bool QNetworkAccessDebugPipeBackend::receive(DataPacket &packet) +{ + if (!canReceive()) + return false; + + // canReceive() does the setting up for us + Q_ASSERT(socket.bytesAvailable() >= incomingPacketSize); + QByteArray incomingPacket = socket.read(incomingPacketSize); + QDataStream stream(&incomingPacket, QIODevice::ReadOnly); + stream.setVersion(QDataStream::Qt_4_4); + stream >> packet.headers >> packet.data; + + // reset for next packet: + incomingPacketSize = 0; + socket.setReadBufferSize(ReadBufferSize); + return true; +} + +bool QNetworkAccessDebugPipeBackend::canReceive() +{ + if (incomingPacketSize == 0) { + // read the packet size + if (quint64(socket.bytesAvailable()) >= sizeof incomingPacketSize) + socket.read((char*)&incomingPacketSize, sizeof incomingPacketSize); + else + return false; + } + + if (incomingPacketSize == 0) { + QString msg = QObject::tr("Protocol error: packet of size 0 received"); + error(QNetworkReply::ProtocolFailure, msg); + finished(); + + socket.blockSignals(true); + socket.abort(); + socket.blockSignals(false); + return false; + } + + return socket.bytesAvailable() >= incomingPacketSize; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessdebugpipebackend_p.h b/src/network/access/qnetworkaccessdebugpipebackend_p.h new file mode 100644 index 0000000..73a35cf --- /dev/null +++ b/src/network/access/qnetworkaccessdebugpipebackend_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSDEBUGPIPEBACKEND_P_H +#define QNETWORKACCESSDEBUGPIPEBACKEND_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 "qnetworkaccessbackend_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qtcpsocket.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_BUILD_INTERNAL + +class QNetworkAccessDebugPipeBackend: public QNetworkAccessBackend +{ + Q_OBJECT +public: + struct DataPacket; + QNetworkAccessDebugPipeBackend(); + virtual ~QNetworkAccessDebugPipeBackend(); + + virtual void open(); + virtual void closeDownstreamChannel(); + virtual void closeUpstreamChannel(); + virtual bool waitForDownstreamReadyRead(int msecs); + virtual bool waitForUpstreamBytesWritten(int msecs); + + virtual void upstreamReadyRead(); + virtual void downstreamReadyWrite(); + +private slots: + void socketReadyRead(); + void socketBytesWritten(qint64 bytes); + void socketError(); + void socketDisconnected(); + +private: + QTcpSocket socket; + qint32 incomingPacketSize; + bool readyReadEmitted; + bool bytesWrittenEmitted; + bool bareProtocol; + + bool send(const DataPacket &packet); + bool canReceive(); + bool receive(DataPacket &packet); +}; + +class QNetworkAccessDebugPipeBackendFactory: public QNetworkAccessBackendFactory +{ +public: + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const; +}; + +#endif // QT_BUILD_INTERNAL + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkaccessfilebackend.cpp b/src/network/access/qnetworkaccessfilebackend.cpp new file mode 100644 index 0000000..8a5a665 --- /dev/null +++ b/src/network/access/qnetworkaccessfilebackend.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessfilebackend_p.h" +#include "qfileinfo.h" +#include "qurlinfo.h" +#include "qdir.h" + +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE + +QNetworkAccessBackend * +QNetworkAccessFileBackendFactory::create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const +{ + // is it an operation we know of? + switch (op) { + case QNetworkAccessManager::GetOperation: + case QNetworkAccessManager::PutOperation: + break; + + default: + // no, we can't handle this operation + return 0; + } + + QUrl url = request.url(); + if (url.scheme() == QLatin1String("qrc") || !url.toLocalFile().isEmpty()) + return new QNetworkAccessFileBackend; + else if (!url.isEmpty() && url.authority().isEmpty()) { + // check if QFile could, in theory, open this URL + QFileInfo fi(url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery)); + if (fi.exists() || (op == QNetworkAccessManager::PutOperation && fi.dir().exists())) + return new QNetworkAccessFileBackend; + } + + return 0; +} + +QNetworkAccessFileBackend::QNetworkAccessFileBackend() + : totalBytes(0) +{ +} + +QNetworkAccessFileBackend::~QNetworkAccessFileBackend() +{ +} + +void QNetworkAccessFileBackend::open() +{ + QUrl url = this->url(); + + if (url.host() == QLatin1String("localhost")) + url.setHost(QString()); +#if !defined(Q_OS_WIN) + // do not allow UNC paths on Unix + if (!url.host().isEmpty()) { + // we handle only local files + error(QNetworkReply::ProtocolInvalidOperationError, + QCoreApplication::translate("QNetworkAccessFileBackend", "Request for opening non-local file %1").arg(url.toString())); + finished(); + return; + } +#endif // !defined(Q_OS_WIN) + if (url.path().isEmpty()) + url.setPath(QLatin1String("/")); + setUrl(url); + + QString fileName = url.toLocalFile(); + if (fileName.isEmpty()) { + if (url.scheme() == QLatin1String("qrc")) + fileName = QLatin1String(":") + url.path(); + else + fileName = url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery); + } + file.setFileName(fileName); + + if (operation() == QNetworkAccessManager::GetOperation) { + if (!loadFileInfo()) + return; + } + + QIODevice::OpenMode mode; + switch (operation()) { + case QNetworkAccessManager::GetOperation: + mode = QIODevice::ReadOnly; + break; + case QNetworkAccessManager::PutOperation: + mode = QIODevice::WriteOnly | QIODevice::Truncate; + break; + default: + Q_ASSERT_X(false, "QNetworkAccessFileBackend::open", + "Got a request operation I cannot handle!!"); + return; + } + + mode |= QIODevice::Unbuffered; + bool opened = file.open(mode); + + // could we open the file? + if (!opened) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2") + .arg(this->url().toString(), file.errorString()); + + // why couldn't we open the file? + // if we're opening for reading, either it doesn't exist, or it's access denied + // if we're opening for writing, not existing means it's access denied too + if (file.exists() || operation() == QNetworkAccessManager::PutOperation) + error(QNetworkReply::ContentAccessDenied, msg); + else + error(QNetworkReply::ContentNotFoundError, msg); + finished(); + } +} + +void QNetworkAccessFileBackend::closeDownstreamChannel() +{ + if (operation() == QNetworkAccessManager::GetOperation) { + file.close(); + //downstreamChannelClosed(); + } +} + +void QNetworkAccessFileBackend::closeUpstreamChannel() +{ + if (operation() == QNetworkAccessManager::PutOperation) { + file.close(); + finished(); + } +} + +bool QNetworkAccessFileBackend::waitForDownstreamReadyRead(int) +{ + Q_ASSERT(operation() == QNetworkAccessManager::GetOperation); + return readMoreFromFile(); +} + +bool QNetworkAccessFileBackend::waitForUpstreamBytesWritten(int) +{ + Q_ASSERT_X(false, "QNetworkAccessFileBackend::waitForUpstreamBytesWritten", + "This function should never have been called, since there is never anything " + "left to be written!"); + return false; +} + +void QNetworkAccessFileBackend::upstreamReadyRead() +{ + Q_ASSERT_X(operation() == QNetworkAccessManager::PutOperation, "QNetworkAccessFileBackend", + "We're being told to upload data but operation isn't PUT!"); + + // there's more data to be written to the file + while (upstreamBytesAvailable()) { + // write everything and let QFile handle it + int written = file.write(readUpstream()); + + if (written < 0) { + // write error! + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Write error writing to %1: %2") + .arg(url().toString(), file.errorString()); + error(QNetworkReply::ProtocolFailure, msg); + + finished(); + return; + } + + // successful write + file.flush(); + upstreamBytesConsumed(written); + } +} + +void QNetworkAccessFileBackend::downstreamReadyWrite() +{ + Q_ASSERT_X(operation() == QNetworkAccessManager::GetOperation, "QNetworkAccessFileBackend", + "We're being told to download data but operation isn't GET!"); + + readMoreFromFile(); +} + +bool QNetworkAccessFileBackend::loadFileInfo() +{ + QFileInfo fi(file); + setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified()); + setHeader(QNetworkRequest::ContentLengthHeader, fi.size()); + + // signal we're open + metaDataChanged(); + + if (fi.isDir()) { + error(QNetworkReply::ContentOperationNotPermittedError, + QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url().toString())); + finished(); + return false; + } + + return true; +} + +bool QNetworkAccessFileBackend::readMoreFromFile() +{ + qint64 wantToRead; + while ((wantToRead = nextDownstreamBlockSize()) > 0) { + // ### FIXME!! + // Obtain a pointer from the ringbuffer! + // Avoid extra copy + QByteArray data; + data.reserve(wantToRead); + qint64 actuallyRead = file.read(data.data(), wantToRead); + if (actuallyRead <= 0) { + // EOF or error + if (file.error() != QFile::NoError) { + QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Read error reading from %1: %2") + .arg(url().toString(), file.errorString()); + error(QNetworkReply::ProtocolFailure, msg); + + finished(); + return false; + } + + finished(); + return true; + } + + data.resize(actuallyRead); + totalBytes += actuallyRead; + writeDownstreamData(data); + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkaccessfilebackend_p.h b/src/network/access/qnetworkaccessfilebackend_p.h new file mode 100644 index 0000000..ce7d351 --- /dev/null +++ b/src/network/access/qnetworkaccessfilebackend_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSFILEBACKEND_P_H +#define QNETWORKACCESSFILEBACKEND_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 "qnetworkaccessbackend_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "QtCore/qfile.h" + +QT_BEGIN_NAMESPACE + +class QNetworkAccessFileBackend: public QNetworkAccessBackend +{ +public: + QNetworkAccessFileBackend(); + virtual ~QNetworkAccessFileBackend(); + + virtual void open(); + virtual void closeDownstreamChannel(); + virtual void closeUpstreamChannel(); + virtual bool waitForDownstreamReadyRead(int msecs); + virtual bool waitForUpstreamBytesWritten(int msecs); + + virtual void upstreamReadyRead(); + virtual void downstreamReadyWrite(); + +private: + QFile file; + qint64 totalBytes; + + bool loadFileInfo(); + bool readMoreFromFile(); +}; + +class QNetworkAccessFileBackendFactory: public QNetworkAccessBackendFactory +{ +public: + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkaccessftpbackend.cpp b/src/network/access/qnetworkaccessftpbackend.cpp new file mode 100644 index 0000000..ea39dec --- /dev/null +++ b/src/network/access/qnetworkaccessftpbackend.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessftpbackend_p.h" +#include "qnetworkaccessmanager_p.h" +#include "QtNetwork/qauthenticator.h" + +#ifndef QT_NO_FTP + +QT_BEGIN_NAMESPACE + +enum { + DefaultFtpPort = 21 +}; + +static QByteArray makeCacheKey(const QUrl &url) +{ + QUrl copy = url; + copy.setPort(url.port(DefaultFtpPort)); + return "ftp-connection:" + + copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath | QUrl::RemoveQuery | + QUrl::RemoveFragment); +} + +QNetworkAccessBackend * +QNetworkAccessFtpBackendFactory::create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const +{ + // is it an operation we know of? + switch (op) { + case QNetworkAccessManager::GetOperation: + case QNetworkAccessManager::PutOperation: + break; + + default: + // no, we can't handle this operation + return 0; + } + + QUrl url = request.url(); + if (url.scheme() == QLatin1String("ftp")) + return new QNetworkAccessFtpBackend; + return 0; +} + +class QNetworkAccessFtpIODevice: public QIODevice +{ + //Q_OBJECT +public: + QNetworkAccessFtpBackend *backend; + bool eof; + + inline QNetworkAccessFtpIODevice(QNetworkAccessFtpBackend *parent) + : QIODevice(parent), backend(parent), eof(false) + { open(ReadOnly); } + + bool isSequential() const { return true; } + bool atEnd() const { return backend->upstreamBytesAvailable() == 0; } + + qint64 bytesAvailable() const { return backend->upstreamBytesAvailable(); } + qint64 bytesToWrite() const { return backend->downstreamBytesToConsume(); } +protected: + qint64 readData(char *data, qint64 maxlen) + { + const QByteArray toSend = backend->readUpstream(); + maxlen = qMin<qint64>(maxlen, toSend.size()); + if (!maxlen) + return eof ? -1 : 0; + + backend->upstreamBytesConsumed(maxlen); + memcpy(data, toSend.constData(), maxlen); + return maxlen; + } + + qint64 writeData(const char *, qint64) + { return -1; } + + friend class QNetworkAccessFtpBackend; +}; + +class QNetworkAccessFtpFtp: public QFtp, public QNetworkAccessCache::CacheableObject +{ + // Q_OBJECT +public: + QNetworkAccessFtpFtp() + { + setExpires(true); + setShareable(false); + } + + void dispose() + { + connect(this, SIGNAL(done(bool)), this, SLOT(deleteLater())); + close(); + } +}; + +QNetworkAccessFtpBackend::QNetworkAccessFtpBackend() + : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1), + supportsSize(false), supportsMdtm(false), state(Idle) +{ +} + +QNetworkAccessFtpBackend::~QNetworkAccessFtpBackend() +{ + disconnectFromFtp(); +} + +void QNetworkAccessFtpBackend::open() +{ +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + foreach (const QNetworkProxy &p, proxyList()) { + // use the first FTP proxy + // or no proxy at all + if (p.type() == QNetworkProxy::FtpCachingProxy + || p.type() == QNetworkProxy::NoProxy) { + proxy = p; + break; + } + } + + // did we find an FTP proxy or a NoProxy? + if (proxy.type() == QNetworkProxy::DefaultProxy) { + // unsuitable proxies + error(QNetworkReply::ProxyNotFoundError, + tr("No suitable proxy found")); + finished(); + return; + } + +#endif + + QUrl url = this->url(); + if (url.path().isEmpty()) { + url.setPath(QLatin1String("/")); + setUrl(url); + } + if (url.path().endsWith(QLatin1Char('/'))) { + error(QNetworkReply::ContentOperationNotPermittedError, + tr("Cannot open %1: is a directory").arg(url.toString())); + finished(); + return; + } + state = LoggingIn; + + QNetworkAccessCache* cache = QNetworkAccessManagerPrivate::getCache(this); + QByteArray cacheKey = makeCacheKey(url); + if (!cache->requestEntry(cacheKey, this, + SLOT(ftpConnectionReady(QNetworkAccessCache::CacheableObject*)))) { + ftp = new QNetworkAccessFtpFtp; +#ifndef QT_NO_NETWORKPROXY + if (proxy.type() == QNetworkProxy::FtpCachingProxy) + ftp->setProxy(proxy.hostName(), proxy.port()); +#endif + ftp->connectToHost(url.host(), url.port(DefaultFtpPort)); + ftp->login(url.userName(), url.password()); + + cache->addEntry(cacheKey, ftp); + ftpConnectionReady(ftp); + } + + uploadDevice = new QNetworkAccessFtpIODevice(this); +} + +void QNetworkAccessFtpBackend::closeDownstreamChannel() +{ + state = Disconnecting; + if (operation() == QNetworkAccessManager::GetOperation) +#ifndef Q_OS_WINCE + abort(); +#else + exit(3); +#endif +} + +void QNetworkAccessFtpBackend::closeUpstreamChannel() +{ + if (operation() == QNetworkAccessManager::PutOperation) { + Q_ASSERT(uploadDevice); + uploadDevice->eof = true; + if (!upstreamBytesAvailable()) + emit uploadDevice->readyRead(); + } +} + +bool QNetworkAccessFtpBackend::waitForDownstreamReadyRead(int ms) +{ + if (!ftp) + return false; + + if (ftp->bytesAvailable()) { + ftpReadyRead(); + return true; + } + + if (ms == 0) + return false; + + qCritical("QNetworkAccess: FTP backend does not support waitForReadyRead()"); + return false; +} + +bool QNetworkAccessFtpBackend::waitForUpstreamBytesWritten(int ms) +{ + Q_UNUSED(ms); + qCritical("QNetworkAccess: FTP backend does not support waitForBytesWritten()"); + return false; +} + +void QNetworkAccessFtpBackend::upstreamReadyRead() +{ + // uh... how does QFtp operate? +} + +void QNetworkAccessFtpBackend::downstreamReadyWrite() +{ + if (state == Transferring && ftp && ftp->bytesAvailable()) + ftpReadyRead(); +} + +void QNetworkAccessFtpBackend::ftpConnectionReady(QNetworkAccessCache::CacheableObject *o) +{ + ftp = static_cast<QNetworkAccessFtpFtp *>(o); + connect(ftp, SIGNAL(done(bool)), SLOT(ftpDone())); + connect(ftp, SIGNAL(rawCommandReply(int,QString)), SLOT(ftpRawCommandReply(int,QString))); + connect(ftp, SIGNAL(readyRead()), SLOT(ftpReadyRead())); + + // is the login process done already? + if (ftp->state() == QFtp::LoggedIn) + ftpDone(); + + // no, defer the actual operation until after we've logged in +} + +void QNetworkAccessFtpBackend::disconnectFromFtp() +{ + state = Disconnecting; + + if (ftp) { + disconnect(ftp, 0, this, 0); + + QByteArray key = makeCacheKey(url()); + QNetworkAccessManagerPrivate::getCache(this)->releaseEntry(key); + + ftp = 0; + } +} + +void QNetworkAccessFtpBackend::ftpDone() +{ + // the last command we sent is done + if (state == LoggingIn && ftp->state() != QFtp::LoggedIn) { + if (ftp->state() == QFtp::Connected) { + // the login did not succeed + QUrl newUrl = url(); + newUrl.setUserInfo(QString()); + setUrl(newUrl); + + QAuthenticator auth; + authenticationRequired(&auth); + + if (!auth.isNull()) { + // try again: + newUrl.setUserName(auth.user()); + ftp->login(auth.user(), auth.password()); + return; + } + + error(QNetworkReply::AuthenticationRequiredError, + tr("Logging in to %1 failed: authentication required") + .arg(url().host())); + } else { + // we did not connect + QNetworkReply::NetworkError code; + switch (ftp->error()) { + case QFtp::HostNotFound: + code = QNetworkReply::HostNotFoundError; + break; + + case QFtp::ConnectionRefused: + code = QNetworkReply::ConnectionRefusedError; + break; + + default: + code = QNetworkReply::ProtocolFailure; + break; + } + + error(code, ftp->errorString()); + } + + // we're not connected, so remove the cache entry: + QByteArray key = makeCacheKey(url()); + QNetworkAccessManagerPrivate::getCache(this)->removeEntry(key); + + disconnect(ftp, 0, this, 0); + ftp->dispose(); + ftp = 0; + + state = Disconnecting; + finished(); + return; + } + + // check for errors: + if (ftp->error() != QFtp::NoError) { + QString msg; + if (operation() == QNetworkAccessManager::GetOperation) + msg = tr("Error while downloading %1: %2"); + else + msg = tr("Error while uploading %1: %2"); + msg = msg.arg(url().toString(), ftp->errorString()); + + if (state == Statting) + // file probably doesn't exist + error(QNetworkReply::ContentNotFoundError, msg); + else + error(QNetworkReply::ContentAccessDenied, msg); + + disconnectFromFtp(); + finished(); + } + + if (state == LoggingIn) { + state = CheckingFeatures; + if (operation() == QNetworkAccessManager::GetOperation) { + // send help command to find out if server supports "SIZE" and "MDTM" + QString command = url().path(); + command.prepend(QLatin1String("%1 ")); + helpId = ftp->rawCommand(QLatin1String("HELP")); // get supported commands + } else { + ftpDone(); + } + } else if (state == CheckingFeatures) { + state = Statting; + if (operation() == QNetworkAccessManager::GetOperation) { + // logged in successfully, send the stat requests (if supported) + QString command = url().path(); + command.prepend(QLatin1String("%1 ")); + if (supportsSize) + sizeId = ftp->rawCommand(command.arg(QLatin1String("SIZE"))); // get size + if (supportsMdtm) + mdtmId = ftp->rawCommand(command.arg(QLatin1String("MDTM"))); // get modified time + if (!supportsSize && !supportsMdtm) + ftpDone(); // no commands sent, move to the next state + } else { + ftpDone(); + } + } else if (state == Statting) { + // statted successfully, send the actual request + emit metaDataChanged(); + state = Transferring; + + QFtp::TransferType type = QFtp::Binary; + if (operation() == QNetworkAccessManager::GetOperation) { + setCachingEnabled(true); + ftp->get(url().path(), 0, type); + } else { + ftp->put(uploadDevice, url().path(), type); + } + + } else if (state == Transferring) { + // upload or download finished + disconnectFromFtp(); + finished(); + } +} + +void QNetworkAccessFtpBackend::ftpReadyRead() +{ + writeDownstreamData(ftp->readAll()); +} + +void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text) +{ + //qDebug() << "FTP reply:" << code << text; + int id = ftp->currentId(); + + if ((id == helpId) && ((code == 200) || (code == 214))) { // supported commands + // the "FEAT" ftp command would be nice here, but it is not part of the + // initial FTP RFC 959, neither ar "SIZE" nor "MDTM" (they are all specified + // in RFC 3659) + if (text.contains(QLatin1String("SIZE"), Qt::CaseSensitive)) + supportsSize = true; + if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive)) + supportsMdtm = true; + } else if (code == 213) { // file status + if (id == sizeId) { + // reply to the size command + setHeader(QNetworkRequest::ContentLengthHeader, text.toLongLong()); +#ifndef QT_NO_DATESTRING + } else if (id == mdtmId) { + QDateTime dt = QDateTime::fromString(text, QLatin1String("yyyyMMddHHmmss")); + setHeader(QNetworkRequest::LastModifiedHeader, dt); +#endif + } + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_FTP diff --git a/src/network/access/qnetworkaccessftpbackend_p.h b/src/network/access/qnetworkaccessftpbackend_p.h new file mode 100644 index 0000000..9ec2dd8 --- /dev/null +++ b/src/network/access/qnetworkaccessftpbackend_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSFTPBACKEND_P_H +#define QNETWORKACCESSFTPBACKEND_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 "qnetworkaccessbackend_p.h" +#include "qnetworkaccesscache_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "QtNetwork/qftp.h" + +#include "QtCore/qpointer.h" + +#ifndef QT_NO_FTP + +QT_BEGIN_NAMESPACE + +class QNetworkAccessFtpIODevice; +class QNetworkAccessFtpFtp; + +class QNetworkAccessFtpBackend: public QNetworkAccessBackend +{ + Q_OBJECT +public: + enum State { + Idle, + //Connecting, + LoggingIn, + CheckingFeatures, + Statting, + Transferring, + Disconnecting + }; + + QNetworkAccessFtpBackend(); + virtual ~QNetworkAccessFtpBackend(); + + virtual void open(); + virtual void closeDownstreamChannel(); + virtual void closeUpstreamChannel(); + virtual bool waitForDownstreamReadyRead(int msecs); + virtual bool waitForUpstreamBytesWritten(int msecs); + + virtual void upstreamReadyRead(); + virtual void downstreamReadyWrite(); + + void disconnectFromFtp(); + +public slots: + void ftpConnectionReady(QNetworkAccessCache::CacheableObject *object); + void ftpDone(); + void ftpReadyRead(); + void ftpRawCommandReply(int code, const QString &text); + +private: + friend class QNetworkAccessFtpIODevice; + QPointer<QNetworkAccessFtpFtp> ftp; + QNetworkAccessFtpIODevice *uploadDevice; + qint64 totalBytes; + int helpId, sizeId, mdtmId; + bool supportsSize, supportsMdtm; + State state; +}; + +class QNetworkAccessFtpBackendFactory: public QNetworkAccessBackendFactory +{ +public: + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FTP + +#endif diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp new file mode 100644 index 0000000..4b41aa7 --- /dev/null +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -0,0 +1,1052 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNETWORKACCESSHTTPBACKEND_DEBUG + +#include "qnetworkaccesshttpbackend_p.h" +#include "qnetworkaccessmanager_p.h" +#include "qnetworkaccesscache_p.h" +#include "qabstractnetworkcache.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qnetworkrequest_p.h" +#include "qnetworkcookie_p.h" +#include "QtCore/qdatetime.h" +#include "QtNetwork/qsslconfiguration.h" + +#ifndef QT_NO_HTTP + +#include <string.h> // for strchr + +QT_BEGIN_NAMESPACE + +enum { + DefaultHttpPort = 80, + DefaultHttpsPort = 443 +}; + +class QNetworkProxy; + +static QByteArray makeCacheKey(QNetworkAccessHttpBackend *backend, QNetworkProxy *proxy) +{ + QByteArray result; + QUrl copy = backend->url(); + bool isEncrypted = copy.scheme().toLower() == QLatin1String("https"); + copy.setPort(copy.port(isEncrypted ? DefaultHttpsPort : DefaultHttpPort)); + result = copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath | + QUrl::RemoveQuery | QUrl::RemoveFragment); + +#ifndef QT_NO_NETWORKPROXY + if (proxy->type() != QNetworkProxy::NoProxy) { + QUrl key; + + switch (proxy->type()) { + case QNetworkProxy::Socks5Proxy: + key.setScheme(QLatin1String("proxy-socks5")); + break; + + case QNetworkProxy::HttpProxy: + case QNetworkProxy::HttpCachingProxy: + key.setScheme(QLatin1String("proxy-http")); + break; + + default: + break; + } + + if (!key.scheme().isEmpty()) { + key.setUserName(proxy->user()); + key.setHost(proxy->hostName()); + key.setPort(proxy->port()); + key.setEncodedQuery(result); + result = key.toEncoded(); + } + } +#endif + + return "http-connection:" + result; +} + +static inline bool isSeparator(register char c) +{ + static const char separators[] = "()<>@,;:\\\"/[]?={}"; + return isLWS(c) || strchr(separators, c) != 0; +} + +// ### merge with nextField in cookiejar.cpp +static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header) +{ + // The HTTP header is of the form: + // header = #1(directives) + // directives = token | value-directive + // value-directive = token "=" (token | quoted-string) + QHash<QByteArray, QByteArray> result; + + int pos = 0; + while (true) { + // skip spaces + pos = nextNonWhitespace(header, pos); + if (pos == header.length()) + return result; // end of parsing + + // pos points to a non-whitespace + int comma = header.indexOf(',', pos); + int equal = header.indexOf('=', pos); + if (comma == pos || equal == pos) + // huh? Broken header. + return result; + + // The key name is delimited by either a comma, an equal sign or the end + // of the header, whichever comes first + int end = comma; + if (end == -1) + end = header.length(); + if (equal != -1 && end > equal) + end = equal; // equal sign comes before comma/end + QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower(); + pos = end + 1; + + if (equal != -1) { + // case: token "=" (token | quoted-string) + // skip spaces + pos = nextNonWhitespace(header, pos); + if (pos == header.length()) + // huh? Broken header + return result; + + QByteArray value; + value.reserve(header.length() - pos); + if (header.at(pos) == '"') { + // case: quoted-string + // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + // qdtext = <any TEXT except <">> + // quoted-pair = "\" CHAR + ++pos; + while (pos < header.length()) { + register char c = header.at(pos); + if (c == '"') { + // end of quoted text + break; + } else if (c == '\\') { + ++pos; + if (pos >= header.length()) + // broken header + return result; + c = header.at(pos); + } + + value += c; + ++pos; + } + } else { + // case: token + while (pos < header.length()) { + register char c = header.at(pos); + if (isSeparator(c)) + break; + value += c; + ++pos; + } + } + + result.insert(key, value); + + // find the comma now: + comma = header.indexOf(',', pos); + if (comma == -1) + return result; // end of parsing + pos = comma + 1; + } else { + // case: token + // key is already set + result.insert(key, QByteArray()); + } + } +} + +QNetworkAccessBackend * +QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const +{ + // check the operation + switch (op) { + case QNetworkAccessManager::GetOperation: + case QNetworkAccessManager::PostOperation: + case QNetworkAccessManager::HeadOperation: + case QNetworkAccessManager::PutOperation: + break; + + default: + // no, we can't handle this request + return 0; + } + + QUrl url = request.url(); + QString scheme = url.scheme().toLower(); + if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) + return new QNetworkAccessHttpBackend; + + return 0; +} + +static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url) +{ + QNetworkReply::NetworkError code; + // we've got an error + switch (httpStatusCode) { + case 401: // Authorization required + code = QNetworkReply::AuthenticationRequiredError; + break; + + case 403: // Access denied + code = QNetworkReply::ContentOperationNotPermittedError; + break; + + case 404: // Not Found + code = QNetworkReply::ContentNotFoundError; + break; + + case 407: + code = QNetworkReply::ProxyAuthenticationRequiredError; + break; + + default: + if (httpStatusCode > 500) { + // some kind of server error + code = QNetworkReply::ProtocolUnknownError; + } else if (httpStatusCode >= 400) { + // content error we did not handle above + code = QNetworkReply::UnknownContentError; + } else { + qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"", + httpStatusCode, qPrintable(url.toString())); + code = QNetworkReply::ProtocolFailure; + } + } + + return code; +} + +class QNetworkAccessHttpBackendCache: public QHttpNetworkConnection, + public QNetworkAccessCache::CacheableObject +{ + // Q_OBJECT +public: + QNetworkAccessHttpBackendCache(const QString &hostName, quint16 port, bool encrypt) + : QHttpNetworkConnection(hostName, port, encrypt) + { + setExpires(true); + setShareable(true); + } + + virtual void dispose() + { +#if 0 // sample code; do this right with the API + Q_ASSERT(!isWorking()); +#endif + delete this; + } +}; + +class QNetworkAccessHttpBackendIODevice: public QIODevice +{ + // Q_OBJECT +public: + bool eof; + QNetworkAccessHttpBackendIODevice(QNetworkAccessHttpBackend *parent) + : QIODevice(parent), eof(false) + { + setOpenMode(ReadOnly); + } + bool isSequential() const { return true; } + qint64 bytesAvailable() const + { return static_cast<QNetworkAccessHttpBackend *>(parent())->upstreamBytesAvailable(); } + +protected: + virtual qint64 readData(char *buffer, qint64 maxlen) + { + qint64 ret = static_cast<QNetworkAccessHttpBackend *>(parent())->deviceReadData(buffer, maxlen); + if (!ret && eof) + return -1; + return ret; + } + + virtual qint64 writeData(const char *, qint64) + { + return -1; // cannot write + } + + friend class QNetworkAccessHttpBackend; +}; + +QNetworkAccessHttpBackend::QNetworkAccessHttpBackend() + : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0) +#ifndef QT_NO_OPENSSL + , pendingSslConfiguration(0), pendingIgnoreSslErrors(false) +#endif +{ +} + +QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend() +{ + if (http) + disconnectFromHttp(); +#ifndef QT_NO_OPENSSL + delete pendingSslConfiguration; +#endif +} + +void QNetworkAccessHttpBackend::disconnectFromHttp() +{ + if (http) { + disconnect(http, 0, this, 0); + QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getCache(this); + cache->releaseEntry(cacheKey); + } + + if (httpReply) + disconnect(httpReply, 0, this, 0); + + http = 0; + httpReply = 0; + cacheKey.clear(); +} + +void QNetworkAccessHttpBackend::finished() +{ + if (http) + disconnectFromHttp(); + // call parent + QNetworkAccessBackend::finished(); +} + +void QNetworkAccessHttpBackend::setupConnection() +{ +#ifndef QT_NO_NETWORKPROXY + connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); +#endif + connect(http, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)), + SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*))); + connect(http, SIGNAL(error(QNetworkReply::NetworkError,QString)), + SLOT(httpError(QNetworkReply::NetworkError,QString))); +#ifndef QT_NO_OPENSSL + connect(http, SIGNAL(sslErrors(QList<QSslError>)), + SLOT(sslErrors(QList<QSslError>))); +#endif +} + +/* + For a given httpRequest + 1) If AlwaysNetwork, return + 2) If we have a cache entry for this url populate headers so the server can return 304 + 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true + */ +void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache) +{ + QNetworkRequest::CacheLoadControl CacheLoadControlAttribute = + (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(); + if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) { + // forced reload from the network + // tell any caching proxy servers to reload too + httpRequest.setHeaderField("Cache-Control", "no-cache"); + httpRequest.setHeaderField("Pragma", "no-cache"); + return; + } + + QAbstractNetworkCache *nc = networkCache(); + if (!nc) + return; // no local cache + + QNetworkCacheMetaData metaData = nc->metaData(url()); + if (!metaData.isValid()) + return; // not in cache + + if (!metaData.saveToDisk()) + return; + + QNetworkHeadersPrivate cacheHeaders; + QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; + cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); + + it = cacheHeaders.findRawHeader("etag"); + if (it != cacheHeaders.rawHeaders.constEnd()) + httpRequest.setHeaderField("If-None-Match", it->second); + + QDateTime lastModified = metaData.lastModified(); + if (lastModified.isValid()) + httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified)); + + it = cacheHeaders.findRawHeader("Cache-Control"); + if (it != cacheHeaders.rawHeaders.constEnd()) { + QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second); + if (cacheControl.contains("must-revalidate")) + return; + } + + /* + * age_value + * is the value of Age: header received by the cache with + * this response. + * date_value + * is the value of the origin server's Date: header + * request_time + * is the (local) time when the cache made the request + * that resulted in this cached response + * response_time + * is the (local) time when the cache received the + * response + * now + * is the current (local) time + */ + QDateTime currentDateTime = QDateTime::currentDateTime(); + int age_value = 0; + it = cacheHeaders.findRawHeader("age"); + if (it != cacheHeaders.rawHeaders.constEnd()) + age_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t(); + + int date_value = 0; + it = cacheHeaders.findRawHeader("date"); + if (it != cacheHeaders.rawHeaders.constEnd()) + date_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t(); + + int now = currentDateTime.toUTC().toTime_t(); + int request_time = now; + int response_time = now; + + int apparent_age = qMax(0, response_time - date_value); + int corrected_received_age = qMax(apparent_age, age_value); + int response_delay = response_time - request_time; + int corrected_initial_age = corrected_received_age + response_delay; + int resident_time = now - response_time; + int current_age = corrected_initial_age + resident_time; + + // RFC 2616 13.2.4 Expiration Calculations + QDateTime expirationDate = metaData.expirationDate(); + if (!expirationDate.isValid()) { + if (lastModified.isValid()) { + int diff = currentDateTime.secsTo(lastModified); + expirationDate = lastModified; + expirationDate.addSecs(diff / 10); + if (httpRequest.headerField("Warning").isEmpty()) { + QDateTime dt; + dt.setTime_t(current_age); + if (dt.daysTo(currentDateTime) > 1) + httpRequest.setHeaderField("Warning", "113"); + } + } + } + + int freshness_lifetime = currentDateTime.secsTo(expirationDate); + bool response_is_fresh = (freshness_lifetime > current_age); + + if (!response_is_fresh && CacheLoadControlAttribute == QNetworkRequest::PreferNetwork) + return; + + loadedFromCache = true; +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + qDebug() << "response_is_fresh" << CacheLoadControlAttribute; +#endif + if (!sendCacheContents(metaData)) + loadedFromCache = false; +} + +void QNetworkAccessHttpBackend::postRequest() +{ + bool loadedFromCache = false; + QHttpNetworkRequest httpRequest; + switch (operation()) { + case QNetworkAccessManager::GetOperation: + httpRequest.setOperation(QHttpNetworkRequest::Get); + validateCache(httpRequest, loadedFromCache); + break; + + case QNetworkAccessManager::HeadOperation: + httpRequest.setOperation(QHttpNetworkRequest::Head); + validateCache(httpRequest, loadedFromCache); + break; + + case QNetworkAccessManager::PostOperation: + invalidateCache(); + httpRequest.setOperation(QHttpNetworkRequest::Post); + uploadDevice = new QNetworkAccessHttpBackendIODevice(this); + break; + + case QNetworkAccessManager::PutOperation: + invalidateCache(); + httpRequest.setOperation(QHttpNetworkRequest::Put); + uploadDevice = new QNetworkAccessHttpBackendIODevice(this); + break; + + default: + break; // can't happen + } + + httpRequest.setData(uploadDevice); + httpRequest.setUrl(url()); + + QList<QByteArray> headers = request().rawHeaderList(); + foreach (const QByteArray &header, headers) + httpRequest.setHeaderField(header, request().rawHeader(header)); + + if (loadedFromCache) + return; // no need to send the request! :) + + httpReply = http->sendRequest(httpRequest); + httpReply->setParent(this); +#ifndef QT_NO_OPENSSL + if (pendingSslConfiguration) + httpReply->setSslConfiguration(*pendingSslConfiguration); + if (pendingIgnoreSslErrors) + httpReply->ignoreSslErrors(); +#endif + + connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead())); + connect(httpReply, SIGNAL(finished()), SLOT(replyFinished())); + connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)), + SLOT(httpError(QNetworkReply::NetworkError,QString))); + connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged())); +} + +void QNetworkAccessHttpBackend::invalidateCache() +{ + QAbstractNetworkCache *nc = networkCache(); + if (nc) + nc->remove(url()); +} + +void QNetworkAccessHttpBackend::open() +{ + QUrl url = request().url(); + bool encrypt = url.scheme().toLower() == QLatin1String("https"); + setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt); + + // set the port number in the reply if it wasn't set + url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort)); + + QNetworkProxy *theProxy = 0; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy transparentProxy, cacheProxy; + + foreach (const QNetworkProxy &p, proxyList()) { + // use the first proxy that works + // for non-encrypted connections, any transparent or HTTP proxy + // for encrypted, only transparent proxies + if (!encrypt + && (p.capabilities() & QNetworkProxy::CachingCapability) + && (p.type() == QNetworkProxy::HttpProxy || + p.type() == QNetworkProxy::HttpCachingProxy)) { + cacheProxy = p; + transparentProxy = QNetworkProxy::NoProxy; + theProxy = &cacheProxy; + break; + } + if (p.isTransparentProxy()) { + transparentProxy = p; + cacheProxy = QNetworkProxy::NoProxy; + theProxy = &transparentProxy; + break; + } + } + + // check if at least one of the proxies + if (transparentProxy.type() == QNetworkProxy::DefaultProxy && + cacheProxy.type() == QNetworkProxy::DefaultProxy) { + // unsuitable proxies + error(QNetworkReply::ProxyNotFoundError, + tr("No suitable proxy found")); + finished(); + return; + } +#endif + + // check if we have an open connection to this host + cacheKey = makeCacheKey(this, theProxy); + QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getCache(this); + if ((http = static_cast<QNetworkAccessHttpBackendCache *>(cache->requestEntryNow(cacheKey))) == 0) { + // no entry in cache; create an object + http = new QNetworkAccessHttpBackendCache(url.host(), url.port(), encrypt); + +#ifndef QT_NO_NETWORKPROXY + http->setTransparentProxy(transparentProxy); + http->setCacheProxy(cacheProxy); +#endif + + cache->addEntry(cacheKey, http); + } + + setupConnection(); + postRequest(); +} + +void QNetworkAccessHttpBackend::closeDownstreamChannel() +{ + // this indicates that the user closed the stream while the reply isn't finished yet +} + +void QNetworkAccessHttpBackend::closeUpstreamChannel() +{ + // this indicates that the user finished uploading the data for POST + Q_ASSERT(uploadDevice); + uploadDevice->eof = true; + emit uploadDevice->readChannelFinished(); +} + +bool QNetworkAccessHttpBackend::waitForDownstreamReadyRead(int msecs) +{ + Q_ASSERT(http); + + if (httpReply->bytesAvailable()) { + readFromHttp(); + return true; + } + + if (msecs == 0) { + // no bytes available in the socket and no waiting + return false; + } + + // ### FIXME + qCritical("QNetworkAccess: HTTP backend does not support waitForReadyRead()"); + return false; +} + +bool QNetworkAccessHttpBackend::waitForUpstreamBytesWritten(int msecs) +{ + + // ### FIXME: not implemented in QHttpNetworkAccess + Q_UNUSED(msecs); + qCritical("QNetworkAccess: HTTP backend does not support waitForBytesWritten()"); + return false; +} + +void QNetworkAccessHttpBackend::upstreamReadyRead() +{ + // There is more data available from the user to be uploaded + // QHttpNetworkAccess implements the upload rate control: + // we simply tell QHttpNetworkAccess that there is more data available + // it'll pull from us when it can (through uploadDevice) + + Q_ASSERT(uploadDevice); + emit uploadDevice->readyRead(); +} + +qint64 QNetworkAccessHttpBackend::deviceReadData(char *buffer, qint64 maxlen) +{ + QByteArray toBeUploaded = readUpstream(); + if (toBeUploaded.isEmpty()) + return 0; // nothing to be uploaded + + maxlen = qMin<qint64>(maxlen, toBeUploaded.length()); + + memcpy(buffer, toBeUploaded.constData(), maxlen); + upstreamBytesConsumed(maxlen); + return maxlen; +} + +void QNetworkAccessHttpBackend::downstreamReadyWrite() +{ + readFromHttp(); + if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished()) + replyFinished(); +} + +void QNetworkAccessHttpBackend::replyReadyRead() +{ + readFromHttp(); +} + +void QNetworkAccessHttpBackend::readFromHttp() +{ + if (!httpReply) + return; + + // We implement the download rate control + // Don't read from QHttpNetworkAccess more than QNetworkAccessBackend wants + // One of the two functions above will be called when we can read again + + qint64 bytesToRead = qBound<qint64>(0, httpReply->bytesAvailable(), nextDownstreamBlockSize()); + if (!bytesToRead) + return; + + QByteArray data = httpReply->read(bytesToRead); + writeDownstreamData(data); +} + +void QNetworkAccessHttpBackend::replyFinished() +{ + if (httpReply->bytesAvailable()) + // we haven't read everything yet. Wait some more. + return; + + int statusCode = httpReply->statusCode(); + if (statusCode >= 400) { + // it's an error reply + QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply", + "Error downloading %1 - server replied: %2")); + msg = msg.arg(url().toString(), httpReply->reasonPhrase()); + error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg); + } + +#ifndef QT_NO_OPENSSL + // store the SSL configuration now + // once we call finished(), we won't have access to httpReply anymore + QSslConfiguration sslConfig = httpReply->sslConfiguration(); + if (pendingSslConfiguration) + *pendingSslConfiguration = sslConfig; + else if (!sslConfig.isNull()) + pendingSslConfiguration = new QSslConfiguration(sslConfig); +#endif + + finished(); +} + +void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode) +{ + switch (statusCode) { + case 301: // Moved Permanently + case 302: // Found + case 303: // See Other + case 307: // Temporary Redirect + // What do we do about the caching of the HTML note? + // The response to a 303 MUST NOT be cached, while the response to + // all of the others is cacheable if the headers indicate it to be + QByteArray header = rawHeader("location"); + QUrl url = QUrl::fromEncoded(header); + if (!url.isValid()) + url = QUrl(QLatin1String(header)); + redirectionRequested(url); + } +} + +void QNetworkAccessHttpBackend::replyHeaderChanged() +{ + // reconstruct the HTTP header + QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header(); + QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(), + end = headerMap.constEnd(); + QByteArray header; + + for (; it != end; ++it) { + QByteArray value = rawHeader(it->first); + if (!value.isEmpty()) + value += ", "; + value += it->second; + setRawHeader(it->first, value); + } + + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode()); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase()); + + // is it a redirection? + const int statusCode = httpReply->statusCode(); + checkForRedirect(statusCode); + + if (statusCode >= 500 && statusCode < 600) { + QAbstractNetworkCache *nc = networkCache(); + if (nc) { + QNetworkCacheMetaData metaData = nc->metaData(url()); + QNetworkHeadersPrivate cacheHeaders; + cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); + QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; + it = cacheHeaders.findRawHeader("Cache-Control"); + bool mustReValidate = false; + if (it != cacheHeaders.rawHeaders.constEnd()) { + QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second); + if (cacheControl.contains("must-revalidate")) + mustReValidate = true; + } + if (!mustReValidate && sendCacheContents(metaData)) + return; + } + } + + if (statusCode == 304) { +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + qDebug() << "Received a 304 from" << url(); +#endif + QAbstractNetworkCache *nc = networkCache(); + if (nc) { + QNetworkCacheMetaData oldMetaData = nc->metaData(url()); + QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData); + if (oldMetaData != metaData) + nc->updateMetaData(metaData); + if (sendCacheContents(metaData)) + return; + } + } + + + if (statusCode != 304 && statusCode != 303) { + if (!isCachingEnabled()) + setCachingEnabled(true); + } + metaDataChanged(); +} + +void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &, + QAuthenticator *auth) +{ + authenticationRequired(auth); +} + +void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode, + const QString &errorString) +{ +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + qDebug() << "http error!" << errorCode << errorString; +#endif +#if 0 + static const QNetworkReply::NetworkError conversionTable[] = { + QNetworkReply::ConnectionRefusedError, + QNetworkReply::RemoteHostClosedError, + QNetworkReply::HostNotFoundError, + QNetworkReply::UnknownNetworkError, // SocketAccessError + QNetworkReply::UnknownNetworkError, // SocketResourceError + QNetworkReply::TimeoutError, // SocketTimeoutError + QNetworkReply::UnknownNetworkError, // DatagramTooLargeError + QNetworkReply::UnknownNetworkError, // NetworkError + QNetworkReply::UnknownNetworkError, // AddressInUseError + QNetworkReply::UnknownNetworkError, // SocketAddressNotAvailableError + QNetworkReply::UnknownNetworkError, // UnsupportedSocketOperationError + QNetworkReply::UnknownNetworkError, // UnfinishedSocketOperationError + QNetworkReply::ProxyAuthenticationRequiredError + }; + QNetworkReply::NetworkError code; + if (int(errorCode) >= 0 && + uint(errorCode) < (sizeof conversionTable / sizeof conversionTable[0])) + code = conversionTable[errorCode]; + else + code = QNetworkReply::UnknownNetworkError; +#endif + error(errorCode, errorString); + finished(); +} + +/* + A simple web page that can be used to test us: http://www.procata.com/cachetest/ + */ +bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData) +{ + setCachingEnabled(false); + if (!metaData.isValid()) + return false; + + QAbstractNetworkCache *nc = networkCache(); + Q_ASSERT(nc); + QIODevice *contents = nc->data(url()); + if (!contents) { +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + qDebug() << "Can not send cache, the contents are 0" << url(); +#endif + return false; + } + contents->setParent(this); + + QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes(); + int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status < 100) + status = 200; // fake it + + checkForRedirect(status); + + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status); + setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute)); + setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true); + + QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders(); + QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(), + end = rawHeaders.constEnd(); + for ( ; it != end; ++it) + setRawHeader(it->first, it->second); + + writeDownstreamData(contents); +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes"; +#endif + if (httpReply) + disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished())); + return true; +} + +void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev) +{ + delete dev; + finished(); +} + +#ifndef QT_NO_OPENSSL +void QNetworkAccessHttpBackend::ignoreSslErrors() +{ + if (httpReply) + httpReply->ignoreSslErrors(); + else + pendingIgnoreSslErrors = true; +} + +void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const +{ + if (httpReply) + config = httpReply->sslConfiguration(); + else if (pendingSslConfiguration) + config = *pendingSslConfiguration; +} + +void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig) +{ + if (httpReply) + httpReply->setSslConfiguration(newconfig); + else if (pendingSslConfiguration) + *pendingSslConfiguration = newconfig; + else + pendingSslConfiguration = new QSslConfiguration(newconfig); +} +#endif + +QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const +{ + QNetworkCacheMetaData metaData = oldMetaData; + + QNetworkHeadersPrivate cacheHeaders; + cacheHeaders.setAllRawHeaders(metaData.rawHeaders()); + QNetworkHeadersPrivate::RawHeadersList::ConstIterator it; + + QList<QByteArray> newHeaders = rawHeaderList(); + foreach (QByteArray header, newHeaders) { + header = header.toLower(); + bool hop_by_hop = + (header == "connection" + || header == "keep-alive" + || header == "proxy-authenticate" + || header == "proxy-authorization" + || header == "te" + || header == "trailers" + || header == "transfer-encoding" + || header == "upgrade"); + if (hop_by_hop) + continue; + + // Don't store Warning 1xx headers + if (header == "warning") { + QByteArray v = rawHeader(header); + if (v.length() == 3 + && v[0] == '1' + && v[1] >= '0' && v[1] <= '9' + && v[2] >= '0' && v[2] <= '9') + continue; + } + +#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG) + QByteArray n = rawHeader(header); + QByteArray o; + it = cacheHeaders.findRawHeader(header); + if (it != cacheHeaders.rawHeaders.constEnd()) + o = (*it).second; + if (n != o && header != "Date") { + qDebug() << "replacing" << header; + qDebug() << "new" << n; + qDebug() << "old" << o; + } +#endif + cacheHeaders.setRawHeader(header, rawHeader(header)); + } + metaData.setRawHeaders(cacheHeaders.rawHeaders); + + bool checkExpired = true; + + QHash<QByteArray, QByteArray> cacheControl; + it = cacheHeaders.findRawHeader("Cache-Control"); + if (it != cacheHeaders.rawHeaders.constEnd()) { + cacheControl = parseHttpOptionHeader(it->second); + QByteArray maxAge = cacheControl.value("max-age"); + if (!maxAge.isEmpty()) { + checkExpired = false; + QDateTime dt = QDateTime::currentDateTime(); + dt = dt.addSecs(maxAge.toInt()); + metaData.setExpirationDate(dt); + } + } + if (checkExpired) { + it = cacheHeaders.findRawHeader("expires"); + if (it != cacheHeaders.rawHeaders.constEnd()) { + QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second); + metaData.setExpirationDate(expiredDateTime); + } + } + + it = cacheHeaders.findRawHeader("last-modified"); + if (it != cacheHeaders.rawHeaders.constEnd()) + metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second)); + + bool canDiskCache = true; // Everything defaults to being cacheable on disk + + // 14.32 + // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client + // had sent "Cache-Control: no-cache". + it = cacheHeaders.findRawHeader("pragma"); + if (it != cacheHeaders.rawHeaders.constEnd() + && it->second == "no-cache") + canDiskCache = false; + + // HTTP/1.1. Check the Cache-Control header + if (cacheControl.contains("no-cache")) + canDiskCache = false; + else if (cacheControl.contains("no-store")) + canDiskCache = false; + + metaData.setSaveToDisk(canDiskCache); + int statusCode = httpReply->statusCode(); + QNetworkCacheMetaData::AttributesMap attributes; + if (statusCode != 304) { + // update the status code + attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase()); + } else { + // this is a redirection, keep the attributes intact + attributes = oldMetaData.attributes(); + } + metaData.setAttributes(attributes); + return metaData; +} + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP diff --git a/src/network/access/qnetworkaccesshttpbackend_p.h b/src/network/access/qnetworkaccesshttpbackend_p.h new file mode 100644 index 0000000..02915e7 --- /dev/null +++ b/src/network/access/qnetworkaccesshttpbackend_p.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSHTTPBACKEND_P_H +#define QNETWORKACCESSHTTPBACKEND_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 "qhttpnetworkconnection_p.h" +#include "qnetworkaccessbackend_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qabstractsocket.h" + +#include "QtCore/qpointer.h" +#include "QtCore/qdatetime.h" + +#ifndef QT_NO_HTTP + +QT_BEGIN_NAMESPACE + +class QNetworkAccessHttpBackendCache; + +class QNetworkAccessHttpBackendIODevice; + +class QNetworkAccessHttpBackend: public QNetworkAccessBackend +{ + Q_OBJECT +public: + QNetworkAccessHttpBackend(); + virtual ~QNetworkAccessHttpBackend(); + + virtual void open(); + virtual void closeDownstreamChannel(); + virtual void closeUpstreamChannel(); + virtual bool waitForDownstreamReadyRead(int msecs); + virtual bool waitForUpstreamBytesWritten(int msecs); + + virtual void upstreamReadyRead(); + virtual void downstreamReadyWrite(); + virtual void copyFinished(QIODevice *); +#ifndef QT_NO_OPENSSL + virtual void ignoreSslErrors(); + + virtual void fetchSslConfiguration(QSslConfiguration &configuration) const; + virtual void setSslConfiguration(const QSslConfiguration &configuration); +#endif + QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const; + + qint64 deviceReadData(char *buffer, qint64 maxlen); + +private slots: + void replyReadyRead(); + void replyFinished(); + void replyHeaderChanged(); + void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth); + void httpError(QNetworkReply::NetworkError error, const QString &errorString); + bool sendCacheContents(const QNetworkCacheMetaData &metaData); + +private: + QHttpNetworkReply *httpReply; + QPointer<QNetworkAccessHttpBackendCache> http; + QByteArray cacheKey; + QNetworkAccessHttpBackendIODevice *uploadDevice; +#ifndef QT_NO_OPENSSL + QSslConfiguration *pendingSslConfiguration; + bool pendingIgnoreSslErrors; +#endif + + void disconnectFromHttp(); + void finished(); // override + void setupConnection(); + void validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache); + void invalidateCache(); + void postRequest(); + void readFromHttp(); + void checkForRedirect(const int statusCode); + + friend class QNetworkAccessHttpBackendIODevice; +}; + +class QNetworkAccessHttpBackendFactory : public QNetworkAccessBackendFactory +{ +public: + virtual QNetworkAccessBackend *create(QNetworkAccessManager::Operation op, + const QNetworkRequest &request) const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP + +#endif diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp new file mode 100644 index 0000000..11e1e46 --- /dev/null +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -0,0 +1,961 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkaccessmanager.h" +#include "qnetworkaccessmanager_p.h" +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include "qnetworkcookie.h" +#include "qabstractnetworkcache.h" + +#include "qnetworkaccesshttpbackend_p.h" +#include "qnetworkaccessftpbackend_p.h" +#include "qnetworkaccessfilebackend_p.h" +#include "qnetworkaccessdatabackend_p.h" +#include "qnetworkaccessdebugpipebackend_p.h" + +#include "QtCore/qbuffer.h" +#include "QtCore/qurl.h" +#include "QtCore/qvector.h" +#include "QtNetwork/qauthenticator.h" +#include "QtNetwork/qsslconfiguration.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_HTTP +Q_GLOBAL_STATIC(QNetworkAccessHttpBackendFactory, httpBackend) +#endif // QT_NO_HTTP +Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend) +Q_GLOBAL_STATIC(QNetworkAccessDataBackendFactory, dataBackend) +#ifndef QT_NO_FTP +Q_GLOBAL_STATIC(QNetworkAccessFtpBackendFactory, ftpBackend) +#endif // QT_NO_FTP + +#ifdef QT_BUILD_INTERNAL +Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend) +#endif + +static void ensureInitialized() +{ +#ifndef QT_NO_HTTP + (void) httpBackend(); +#endif // QT_NO_HTTP + (void) dataBackend(); +#ifndef QT_NO_FTP + (void) ftpBackend(); +#endif + +#ifdef QT_BUILD_INTERNAL + (void) debugpipeBackend(); +#endif + + // leave this one last since it will query the special QAbstractFileEngines + (void) fileBackend(); +} + +/*! + \class QNetworkAccessManager + \brief The QNetworkAccessManager class allows the application to + post network requests and receive replies + \since 4.4 + + \inmodule QtNetwork + \reentrant + + The Network Access API is constructed around one QNetworkAccessManager + object, which holds the common configuration and settings for the requests + it sends. It contains the proxy and cache configuration, as well as the + signals related to such issues, and reply signals that can be used to + monitor the progress of a network operation. + + Once a QNetworkAccessManager object has been created, the application can + use it to send requests over the network. A group of standard functions + are supplied that take a request and optional data, and each return a + QNetworkReply object. The returned object is used to obtain any data + returned in response to the corresponding request. + the reply to is where most of the signals as well + as the downloaded data are posted. + + A simple download off the network could be accomplished with: + \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 0 + + When the \tt replyFinished slot above is called, the parameter it + takes is the QNetworkReply object containing the downloaded data + as well as meta-data (headers, etc.). + + \note The slot is responsible for deleting the object at that point. + + A more involved example, assuming the manager is already existent, + can be: + \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 1 + + \sa QNetworkRequest, QNetworkReply, QNetworkProxy +*/ + +/*! + \enum QNetworkAccessManager::Operation + + Indicates the operation this reply is processing. + + \value HeadOperation retrieve headers operation (created + with head()) + + \value GetOperation retrieve headers and download contents + (created with get()) + + \value PutOperation upload contents operation (created + with put()) + + \value PostOperation send the contents of an HTML form for + processing via HTTP POST (created with post()) + + \omitvalue UnknownOperation + + \sa QNetworkReply::operation() +*/ + +/*! + \fn void QNetworkAccessManager::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + + This signal is emitted whenever a proxy requests authentication + and QNetworkAccessManager cannot find a valid, cached + credential. The slot connected to this signal should fill in the + credentials for the proxy \a proxy in the \a authenticator object. + + QNetworkAccessManager will cache the credentials internally. The + next time the proxy requests authentication, QNetworkAccessManager + will automatically send the same credential without emitting the + proxyAuthenticationRequired signal again. + + If the proxy rejects the credentials, QNetworkAccessManager will + emit the signal again. + + \sa proxy(), setProxy(), authenticationRequired() +*/ + +/*! + \fn void QNetworkAccessManager::authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) + + This signal is emitted whenever a final server requests + authentication before it delivers the requested contents. The slot + connected to this signal should fill the credentials for the + contents (which can be determined by inspecting the \a reply + object) in the \a authenticator object. + + QNetworkAccessManager will cache the credentials internally and + will send the same values if the server requires authentication + again, without emitting the authenticationRequired() signal. If it + rejects the credentials, this signal will be emitted again. + + \sa proxyAuthenticationRequired() +*/ + +/*! + \fn void QNetworkAccessManager::finished(QNetworkReply *reply) + + This signal is emitted whenever a pending network reply is + finished. The \a reply parameter will contain a pointer to the + reply that has just finished. This signal is emitted in tandem + with the QNetworkReply::finished() signal. + + See QNetworkReply::finished() for information on the status that + the object will be in. + + \sa QNetworkReply::finished(), QNetworkReply::error() +*/ + +/*! + \fn void QNetworkAccessManager::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors) + + This signal is emitted if the SSL/TLS session encountered errors + during the set up, including certificate verification errors. The + \a errors parameter contains the list of errors and \a reply is + the QNetworkReply that is encountering these errors. + + To indicate that the errors are not fatal and that the connection + should proceed, the QNetworkReply::ignoreSslErrors() function should be called + from the slot connected to this signal. If it is not called, the + SSL session will be torn down before any data is exchanged + (including the URL). + + This signal can be used to display an error message to the user + indicating that security may be compromised and display the + SSL settings (see sslConfiguration() to obtain it). If the user + decides to proceed after analyzing the remote certificate, the + slot should call ignoreSslErrors(). + + \sa QSslSocket::sslErrors(), QNetworkReply::sslErrors(), + QNetworkReply::sslConfiguration(), QNetworkReply::ignoreSslErrors() +*/ + +class QNetworkAuthenticationCredential +{ +public: + QString domain; + QString user; + QString password; +}; +Q_DECLARE_TYPEINFO(QNetworkAuthenticationCredential, Q_MOVABLE_TYPE); +inline bool operator<(const QNetworkAuthenticationCredential &t1, const QString &t2) +{ return t1.domain < t2; } + +class QNetworkAuthenticationCache: private QVector<QNetworkAuthenticationCredential>, + public QNetworkAccessCache::CacheableObject +{ +public: + QNetworkAuthenticationCache() + { + setExpires(false); + setShareable(true); + reserve(1); + } + + QNetworkAuthenticationCredential *findClosestMatch(const QString &domain) + { + iterator it = qLowerBound(begin(), end(), domain); + if (it == end() && !isEmpty()) + --it; + if (it == end() || !domain.startsWith(it->domain)) + return 0; + return &*it; + } + + void insert(const QString &domain, const QString &user, const QString &password) + { + QNetworkAuthenticationCredential *closestMatch = findClosestMatch(domain); + if (closestMatch && closestMatch->domain == domain) { + // we're overriding the current credentials + closestMatch->user = user; + closestMatch->password = password; + } else { + QNetworkAuthenticationCredential newCredential; + newCredential.domain = domain; + newCredential.user = user; + newCredential.password = password; + + if (closestMatch) + QVector<QNetworkAuthenticationCredential>::insert(++closestMatch, newCredential); + else + QVector<QNetworkAuthenticationCredential>::insert(end(), newCredential); + } + } + + virtual void dispose() { delete this; } +}; + +#ifndef QT_NO_NETWORKPROXY +static QByteArray proxyAuthenticationKey(const QNetworkProxy &proxy, const QString &realm) +{ + QUrl key; + + switch (proxy.type()) { + case QNetworkProxy::Socks5Proxy: + key.setScheme(QLatin1String("proxy-socks5")); + break; + + case QNetworkProxy::HttpProxy: + case QNetworkProxy::HttpCachingProxy: + key.setScheme(QLatin1String("proxy-http")); + break; + + case QNetworkProxy::FtpCachingProxy: + key.setScheme(QLatin1String("proxy-ftp")); + + case QNetworkProxy::DefaultProxy: + case QNetworkProxy::NoProxy: + // shouldn't happen + return QByteArray(); + + // no default: + // let there be errors if a new proxy type is added in the future + } + + if (key.scheme().isEmpty()) + // proxy type not handled + return QByteArray(); + + key.setUserName(proxy.user()); + key.setHost(proxy.hostName()); + key.setPort(proxy.port()); + key.setFragment(realm); + return "auth:" + key.toEncoded(); +} +#endif + +static inline QByteArray authenticationKey(const QUrl &url, const QString &realm) +{ + QUrl copy = url; + copy.setFragment(realm); + return "auth:" + copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath | QUrl::RemoveQuery); +} + +/*! + Constructs a QNetworkAccessManager object that is the center of + the Network Access API and sets \a parent as the parent object. +*/ +QNetworkAccessManager::QNetworkAccessManager(QObject *parent) + : QObject(*new QNetworkAccessManagerPrivate, parent) +{ + ensureInitialized(); +} + +/*! + Destroys the QNetworkAccessManager object and frees up any + resources. Note that QNetworkReply objects that are returned from + this class have this object set as their parents, which means that + they will be deleted along with it if you don't call + QObject::setParent() on them. +*/ +QNetworkAccessManager::~QNetworkAccessManager() +{ +#ifndef QT_NO_NETWORKPROXY + delete d_func()->proxyFactory; +#endif +} + +#ifndef QT_NO_NETWORKPROXY +/*! + Returns the QNetworkProxy that the requests sent using this + QNetworkAccessManager object will use. The default value for the + proxy is QNetworkProxy::DefaultProxy. + + \sa setProxy(), setProxyFactory(), proxyAuthenticationRequired() +*/ +QNetworkProxy QNetworkAccessManager::proxy() const +{ + return d_func()->proxy; +} + +/*! + Sets the proxy to be used in future requests to be \a proxy. This + does not affect requests that have already been sent. The + proxyAuthenticationRequired() signal will be emitted if the proxy + requests authentication. + + A proxy set with this function will be used for all requests + issued by QNetworkAccessManager. In some cases, it might be + necessary to select different proxies depending on the type of + request being sent or the destination host. If that's the case, + you should consider using setProxyFactory(). + + \sa proxy(), proxyAuthenticationRequired() +*/ +void QNetworkAccessManager::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QNetworkAccessManager); + delete d->proxyFactory; + d->proxy = proxy; + d->proxyFactory = 0; +} + +/*! + \fn QNetworkProxyFactory *QNetworkAccessManager::proxyFactory() const + \since 4.5 + + Returns the proxy factory that this QNetworkAccessManager object + is using to determine the proxies to be used for requests. + + Note that the pointer returned by this function is managed by + QNetworkAccessManager and could be deleted at any time. + + \sa setProxyFactory(), proxy() +*/ +QNetworkProxyFactory *QNetworkAccessManager::proxyFactory() const +{ + return d_func()->proxyFactory; +} + +/*! + \since 4.5 + + Sets the proxy factory for this class to be \a factory. A proxy + factory is used to determine a more specific list of proxies to be + used for a given request, instead of trying to use the same proxy + value for all requests. + + All queries sent by QNetworkAccessManager will have type + QNetworkProxyQuery::UrlRequest. + + For example, a proxy factory could apply the following rules: + \list + \o if the target address is in the local network (for example, + if the hostname contains no dots or if it's an IP address in + the organization's range), return QNetworkProxy::NoProxy + \o if the request is FTP, return an FTP proxy + \o if the request is HTTP or HTTPS, then return an HTTP proxy + \o otherwise, return a SOCKSv5 proxy server + \endlist + + The lifetime of the object \a factory will be managed by + QNetworkAccessManager. It will delete the object when necessary. + + \note If a specific proxy is set with setProxy(), the factory will not + be used. + + \sa proxyFactory(), setProxy(), QNetworkProxyQuery +*/ +void QNetworkAccessManager::setProxyFactory(QNetworkProxyFactory *factory) +{ + Q_D(QNetworkAccessManager); + delete d->proxyFactory; + d->proxyFactory = factory; + d->proxy = QNetworkProxy(); +} +#endif + +/*! + \since 4.5 + + Returns the cache that is used to store data obtained from the network. + + \sa setCache() +*/ +QAbstractNetworkCache *QNetworkAccessManager::cache() const +{ + Q_D(const QNetworkAccessManager); + return d->networkCache; +} + +/*! + \since 4.5 + + Sets the manager's network cache to be the \a cache specified. The cache + is used for all requests dispatched by the manager. + + Use this function to set the network cache object to a class that implements + additional features, like saving the cookies to permanent storage. + + \note QNetworkAccessManager takes ownership of the \a cache object. + + QNetworkAccessManager by default does not have a set cache. + Qt provides a simple disk cache, QNetworkDiskCache, which can be used. + + \sa cache(), QNetworkRequest::CacheLoadControl +*/ +void QNetworkAccessManager::setCache(QAbstractNetworkCache *cache) +{ + Q_D(QNetworkAccessManager); + if (d->networkCache != cache) { + delete d->networkCache; + d->networkCache = cache; + d->networkCache->setParent(this); + } +} + +/*! + Returns the QNetworkCookieJar that is used to store cookies + obtained from the network as well as cookies that are about to be + sent. + + \sa setCookieJar() +*/ +QNetworkCookieJar *QNetworkAccessManager::cookieJar() const +{ + Q_D(const QNetworkAccessManager); + if (!d->cookieJar) + d->createCookieJar(); + return d->cookieJar; +} + +/*! + Sets the manager's cookie jar to be the \a cookieJar specified. + The cookie jar is used by all requests dispatched by the manager. + + Use this function to set the cookie jar object to a class that + implements additional features, like saving the cookies to permanent + storage. + + \note QNetworkAccessManager takes ownership of the \a cookieJar object. + + QNetworkAccessManager will set the parent of the \a cookieJar + passed to itself, so that the cookie jar is deleted when this + object is deleted as well. If you want to share cookie jars + between different QNetworkAccessManager objects, you may want to + set the cookie jar's parent to 0 after calling this function. + + QNetworkAccessManager by default does not implement any cookie + policy of its own: it accepts all cookies sent by the server, as + long as they are well formed and meet the minimum security + requirements (cookie domain matches the request's and cookie path + matches the request's). In order to implement your own security + policy, override the QNetworkCookieJar::cookiesForUrl() and + QNetworkCookieJar::setCookiesFromUrl() virtual functions. Those + functions are called by QNetworkAccessManager when it detects a + new cookie. + + \sa cookieJar(), QNetworkCookieJar::cookiesForUrl(), QNetworkCookieJar::setCookiesFromUrl() +*/ +void QNetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar) +{ + Q_D(QNetworkAccessManager); + d->cookieJarCreated = true; + if (d->cookieJar != cookieJar) { + if (d->cookieJar && d->cookieJar->parent() == this) + delete d->cookieJar; + d->cookieJar = cookieJar; + d->cookieJar->setParent(this); + } +} + +/*! + This function is used to post a request to obtain the network + headers for \a request. It takes its name after the HTTP request + associated (HEAD). It returns a new QNetworkReply object which + will contain such headers. +*/ +QNetworkReply *QNetworkAccessManager::head(const QNetworkRequest &request) +{ + return d_func()->postProcess(createRequest(QNetworkAccessManager::HeadOperation, request)); +} + +/*! + This function is used to post a request to obtain the contents of + the target \a request. It will cause the contents to be + downloaded, along with the headers associated with it. It returns + a new QNetworkReply object opened for reading which emits its + QIODevice::readyRead() signal whenever new data arrives. + + \sa post(), put() +*/ +QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request) +{ + return d_func()->postProcess(createRequest(QNetworkAccessManager::GetOperation, request)); +} + +/*! + This function is used to send an HTTP POST request to the + destination specified by \a request. The contents of the \a data + device will be uploaded to the server. + + \a data must be opened for reading when this function is called + and must remain valid until the finished() signal is emitted for + this reply. + + The returned QNetworkReply object will be open for reading and + will contain the reply sent by the server to the POST request. + + Note: sending a POST request on protocols other than HTTP and + HTTPS is undefined and will probably fail. + + \sa get(), put() +*/ +QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QIODevice *data) +{ + return d_func()->postProcess(createRequest(QNetworkAccessManager::PostOperation, request, data)); +} + +/*! + \overload + This function sends the contents of the \a data byte array to the + destination specified by \a request. +*/ +QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, const QByteArray &data) +{ + QBuffer *buffer = new QBuffer; + buffer->setData(data); + buffer->open(QIODevice::ReadOnly); + + QNetworkReply *reply = post(request, buffer); + buffer->setParent(reply); + return reply; +} + +/*! + This function is used to upload the contents of \a data to the + destination \a request. + + \a data must be opened for reading when this function is called + and must remain valid until the finished() signal is emitted for + this reply. + + The returned QNetworkReply object will be open for reply, but + whether anything will be available for reading is protocol + dependent. For HTTP, the server may send a small HTML page + indicating the upload was successful (or not). Other protocols + will probably have content in their replies. + + For HTTP, this request will send a PUT request, which most servers + do not allow. Form upload mechanisms, including that of uploading + files through HTML forms, use the POST mechanism. + + \sa get(), post() +*/ +QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QIODevice *data) +{ + return d_func()->postProcess(createRequest(QNetworkAccessManager::PutOperation, request, data)); +} + +/*! + \overload + This function 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) +{ + QBuffer *buffer = new QBuffer; + buffer->setData(data); + buffer->open(QIODevice::ReadOnly); + + QNetworkReply *reply = put(request, buffer); + buffer->setParent(reply); + return reply; +} + +/*! + Returns a new QNetworkReply object to handle the operation \a op + and request \a req. The device \a outgoingData is always 0 for Get and + Head requests, but is the value passed to post() and put() in + those operations (the QByteArray variants will pass a QBuffer + object). + + The default implementation calls QNetworkCookieJar::cookiesForUrl() + on the cookie jar set with setCookieJar() to obtain the cookies to + be sent to the remote server. + + The returned object must be in an open state. +*/ +QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, + const QNetworkRequest &req, + QIODevice *outgoingData) +{ + Q_D(QNetworkAccessManager); + QNetworkRequest request = req; + if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() && + outgoingData && !outgoingData->isSequential()) { + // request has no Content-Length + // but the data that is outgoing is random-access + request.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData->size()); + } + if (d->cookieJar) { + QList<QNetworkCookie> cookies = d->cookieJar->cookiesForUrl(request.url()); + if (!cookies.isEmpty()) + request.setHeader(QNetworkRequest::CookieHeader, qVariantFromValue(cookies)); + } + + // first step: create the reply + QUrl url = request.url(); + QNetworkReplyImpl *reply = new QNetworkReplyImpl(this); + QNetworkReplyImplPrivate *priv = reply->d_func(); + priv->manager = this; + + // second step: fetch cached credentials + QNetworkAuthenticationCredential *cred = d->fetchCachedCredentials(url); + if (cred) { + url.setUserName(cred->user); + url.setPassword(cred->password); + priv->urlForLastAuthentication = url; + } + + // third step: setup the reply + priv->setup(op, request, outgoingData); + if (request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt() != + QNetworkRequest::AlwaysNetwork) + priv->setNetworkCache(d->networkCache); +#ifndef QT_NO_NETWORKPROXY + QList<QNetworkProxy> proxyList = d->queryProxy(QNetworkProxyQuery(request.url())); + priv->proxyList = proxyList; +#endif + + // fourth step: find a backend + priv->backend = d->findBackend(op, request); + if (priv->backend) { + priv->backend->setParent(reply); + priv->backend->reply = priv; + } + +#ifndef QT_NO_OPENSSL + reply->setSslConfiguration(request.sslConfiguration()); +#endif + return reply; +} + +void QNetworkAccessManagerPrivate::_q_replyFinished() +{ + Q_Q(QNetworkAccessManager); + QNetworkReply *reply = qobject_cast<QNetworkReply *>(q->sender()); + if (reply) + emit q->finished(reply); +} + +void QNetworkAccessManagerPrivate::_q_replySslErrors(const QList<QSslError> &errors) +{ +#ifndef QT_NO_OPENSSL + Q_Q(QNetworkAccessManager); + QNetworkReply *reply = qobject_cast<QNetworkReply *>(q->sender()); + if (reply) + emit q->sslErrors(reply, errors); +#else + Q_UNUSED(errors); +#endif +} + +QNetworkReply *QNetworkAccessManagerPrivate::postProcess(QNetworkReply *reply) +{ + Q_Q(QNetworkAccessManager); + QNetworkReplyPrivate::setManager(reply, q); + q->connect(reply, SIGNAL(finished()), SLOT(_q_replyFinished())); +#ifndef QT_NO_OPENSSL + /* In case we're compiled without SSL support, we don't have this signal and we need to + * avoid getting a connection error. */ + q->connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(_q_replySslErrors(QList<QSslError>))); +#endif + + return reply; +} + +void QNetworkAccessManagerPrivate::createCookieJar() const +{ + if (!cookieJarCreated) { + // keep the ugly hack in here + QNetworkAccessManagerPrivate *that = const_cast<QNetworkAccessManagerPrivate *>(this); + that->cookieJarCreated = true; + that->cookieJar = new QNetworkCookieJar(that->q_func()); + } +} + +void QNetworkAccessManagerPrivate::authenticationRequired(QNetworkAccessBackend *backend, + QAuthenticator *authenticator) +{ + Q_Q(QNetworkAccessManager); + + // FIXME: Add support for domains (i.e., the leading path) + QUrl url = backend->reply->url; + + // don't try the cache for the same URL twice in a row + // being called twice for the same URL means the authentication failed + if (url != backend->reply->urlForLastAuthentication) { + QNetworkAuthenticationCredential *cred = fetchCachedCredentials(url, authenticator); + if (cred) { + authenticator->setUser(cred->user); + authenticator->setPassword(cred->password); + backend->reply->urlForLastAuthentication = url; + return; + } + } + + backend->reply->urlForLastAuthentication = url; + emit q->authenticationRequired(backend->reply->q_func(), authenticator); + addCredentials(url, authenticator); +} + +#ifndef QT_NO_NETWORKPROXY +void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBackend *backend, + const QNetworkProxy &proxy, + QAuthenticator *authenticator) +{ + Q_Q(QNetworkAccessManager); + + if (proxy != backend->reply->lastProxyAuthentication) { + QNetworkAuthenticationCredential *cred = fetchCachedCredentials(proxy); + if (cred) { + authenticator->setUser(cred->user); + authenticator->setPassword(cred->password); + return; + } + } + + backend->reply->lastProxyAuthentication = proxy; + emit q->proxyAuthenticationRequired(proxy, authenticator); + addCredentials(proxy, authenticator); +} + +void QNetworkAccessManagerPrivate::addCredentials(const QNetworkProxy &p, + const QAuthenticator *authenticator) +{ + Q_ASSERT(authenticator); + Q_ASSERT(p.type() != QNetworkProxy::DefaultProxy); + Q_ASSERT(p.type() != QNetworkProxy::NoProxy); + + QString realm = authenticator->realm(); + QNetworkProxy proxy = p; + proxy.setUser(authenticator->user()); + // Set two credentials: one with the username and one without + do { + // Set two credentials actually: one with and one without the realm + do { + QByteArray cacheKey = proxyAuthenticationKey(proxy, realm); + if (cacheKey.isEmpty()) + return; // should not happen + + QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache; + auth->insert(QString(), authenticator->user(), authenticator->password()); + cache.addEntry(cacheKey, auth); // replace the existing one, if there's any + + if (realm.isEmpty()) { + break; + } else { + realm.clear(); + } + } while (true); + + if (proxy.user().isEmpty()) + break; + else + proxy.setUser(QString()); + } while (true); +} + +QNetworkAuthenticationCredential * +QNetworkAccessManagerPrivate::fetchCachedCredentials(const QNetworkProxy &p, + const QAuthenticator *authenticator) +{ + QNetworkProxy proxy = p; + if (proxy.type() == QNetworkProxy::DefaultProxy) { + proxy = QNetworkProxy::applicationProxy(); + } + if (!proxy.password().isEmpty()) + return 0; // no need to set credentials if it already has them + + QString realm; + if (authenticator) + realm = authenticator->realm(); + + QByteArray cacheKey = proxyAuthenticationKey(proxy, realm); + if (cacheKey.isEmpty()) + return 0; + if (!cache.hasEntry(cacheKey)) + return 0; + + QNetworkAuthenticationCache *auth = + static_cast<QNetworkAuthenticationCache *>(cache.requestEntryNow(cacheKey)); + QNetworkAuthenticationCredential *cred = auth->findClosestMatch(QString()); + cache.releaseEntry(cacheKey); + + // proxy cache credentials always have exactly one item + Q_ASSERT_X(cred, "QNetworkAccessManager", + "Internal inconsistency: found a cache key for a proxy, but it's empty"); + return cred; +} + +QList<QNetworkProxy> QNetworkAccessManagerPrivate::queryProxy(const QNetworkProxyQuery &query) +{ + QList<QNetworkProxy> proxies; + if (proxyFactory) { + proxies = proxyFactory->queryProxy(query); + if (proxies.isEmpty()) { + qWarning("QNetworkAccessManager: factory %p has returned an empty result set", + proxyFactory); + proxies << QNetworkProxy::NoProxy; + } + } else if (proxy.type() == QNetworkProxy::DefaultProxy) { + // no proxy set, query the application + return QNetworkProxyFactory::proxyForQuery(query); + } else { + proxies << proxy; + } + + return proxies; +} +#endif + +void QNetworkAccessManagerPrivate::addCredentials(const QUrl &url, + const QAuthenticator *authenticator) +{ + Q_ASSERT(authenticator); + QString domain = QString::fromLatin1("/"); // FIXME: make QAuthenticator return the domain + QString realm = authenticator->realm(); + + // Set two credentials actually: one with and one without the username in the URL + QUrl copy = url; + copy.setUserName(authenticator->user()); + do { + QByteArray cacheKey = authenticationKey(copy, realm); + if (cache.hasEntry(cacheKey)) { + QNetworkAuthenticationCache *auth = + static_cast<QNetworkAuthenticationCache *>(cache.requestEntryNow(cacheKey)); + auth->insert(domain, authenticator->user(), authenticator->password()); + cache.releaseEntry(cacheKey); + } else { + QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache; + auth->insert(domain, authenticator->user(), authenticator->password()); + cache.addEntry(cacheKey, auth); + } + + if (copy.userName().isEmpty()) { + break; + } else { + copy.setUserName(QString()); + } + } while (true); +} + +/*! + Fetch the credential data from the credential cache. + + If auth is 0 (as it is when called from createRequest()), this will try to + look up with an empty realm. That fails in most cases for HTTP (because the + realm is seldom empty for HTTP challenges). In any case, QHttpNetworkConnection + never sends the credentials on the first attempt: it needs to find out what + authentication methods the server supports. + + For FTP, realm is always empty. +*/ +QNetworkAuthenticationCredential * +QNetworkAccessManagerPrivate::fetchCachedCredentials(const QUrl &url, + const QAuthenticator *authentication) +{ + if (!url.password().isEmpty()) + return 0; // no need to set credentials if it already has them + + QString realm; + if (authentication) + realm = authentication->realm(); + + QByteArray cacheKey = authenticationKey(url, realm); + if (!cache.hasEntry(cacheKey)) + return 0; + + QNetworkAuthenticationCache *auth = + static_cast<QNetworkAuthenticationCache *>(cache.requestEntryNow(cacheKey)); + QNetworkAuthenticationCredential *cred = auth->findClosestMatch(url.path()); + cache.releaseEntry(cacheKey); + return cred; +} + +void QNetworkAccessManagerPrivate::clearCache(QNetworkAccessManager *manager) +{ + manager->d_func()->cache.clear(); +} + +QT_END_NAMESPACE + +#include "moc_qnetworkaccessmanager.cpp" diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h new file mode 100644 index 0000000..4fe218e --- /dev/null +++ b/src/network/access/qnetworkaccessmanager.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSMANAGER_H +#define QNETWORKACCESSMANAGER_H + +#include <QtCore/QObject> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QIODevice; +class QAbstractNetworkCache; +class QAuthenticator; +class QByteArray; +template<typename T> class QList; +class QNetworkCookie; +class QNetworkCookieJar; +class QNetworkRequest; +class QNetworkReply; +class QNetworkProxy; +class QNetworkProxyFactory; +class QSslError; + +class QNetworkReplyImplPrivate; +class QNetworkAccessManagerPrivate; +class Q_NETWORK_EXPORT QNetworkAccessManager: public QObject +{ + Q_OBJECT +public: + enum Operation { + HeadOperation = 1, + GetOperation, + PutOperation, + PostOperation, + + UnknownOperation = 0 + }; + + explicit QNetworkAccessManager(QObject *parent = 0); + ~QNetworkAccessManager(); + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy() const; + void setProxy(const QNetworkProxy &proxy); + QNetworkProxyFactory *proxyFactory() const; + void setProxyFactory(QNetworkProxyFactory *factory); +#endif + + QAbstractNetworkCache *cache() const; + void setCache(QAbstractNetworkCache *cache); + + QNetworkCookieJar *cookieJar() const; + void setCookieJar(QNetworkCookieJar *cookieJar); + + QNetworkReply *head(const QNetworkRequest &request); + QNetworkReply *get(const QNetworkRequest &request); + QNetworkReply *post(const QNetworkRequest &request, QIODevice *data); + QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data); + QNetworkReply *put(const QNetworkRequest &request, QIODevice *data); + QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data); + +Q_SIGNALS: +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + void authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator); + void finished(QNetworkReply *reply); +#ifndef QT_NO_OPENSSL + void sslErrors(QNetworkReply *reply, const QList<QSslError> &errors); +#endif + +protected: + virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, + QIODevice *outgoingData = 0); + +private: + friend class QNetworkReplyImplPrivate; + Q_DECLARE_PRIVATE(QNetworkAccessManager) + Q_PRIVATE_SLOT(d_func(), void _q_replyFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_replySslErrors(QList<QSslError>)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h new file mode 100644 index 0000000..665d143 --- /dev/null +++ b/src/network/access/qnetworkaccessmanager_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKACCESSMANAGER_P_H +#define QNETWORKACCESSMANAGER_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 "qnetworkaccessmanager.h" +#include "qnetworkaccesscache_p.h" +#include "qnetworkaccessbackend_p.h" +#include "private/qobject_p.h" +#include "QtNetwork/qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QAuthenticator; +class QAbstractNetworkCache; +class QNetworkAuthenticationCredential; +class QNetworkCookieJar; + +class QNetworkAccessManagerPrivate: public QObjectPrivate +{ +public: + QNetworkAccessManagerPrivate() + : networkCache(0), cookieJar(0), +#ifndef QT_NO_NETWORKPROXY + proxyFactory(0), +#endif + cookieJarCreated(false) + { } + + void _q_replyFinished(); + void _q_replySslErrors(const QList<QSslError> &errors); + QNetworkReply *postProcess(QNetworkReply *reply); + void createCookieJar() const; + + void authenticationRequired(QNetworkAccessBackend *backend, QAuthenticator *authenticator); + void addCredentials(const QUrl &url, const QAuthenticator *auth); + QNetworkAuthenticationCredential *fetchCachedCredentials(const QUrl &url, + const QAuthenticator *auth = 0); + +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(QNetworkAccessBackend *backend, const QNetworkProxy &proxy, + QAuthenticator *authenticator); + void addCredentials(const QNetworkProxy &proxy, const QAuthenticator *auth); + QNetworkAuthenticationCredential *fetchCachedCredentials(const QNetworkProxy &proxy, + const QAuthenticator *auth = 0); + QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query); +#endif + + QNetworkAccessBackend *findBackend(QNetworkAccessManager::Operation op, const QNetworkRequest &request); + + QAbstractNetworkCache *networkCache; + QNetworkCookieJar *cookieJar; + + QNetworkAccessCache cache; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxyFactory *proxyFactory; +#endif + + bool cookieJarCreated; + + static inline QNetworkAccessCache *getCache(QNetworkAccessBackend *backend) + { return &backend->manager->cache; } + Q_AUTOTEST_EXPORT static void clearCache(QNetworkAccessManager *manager); + + Q_DECLARE_PUBLIC(QNetworkAccessManager) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp new file mode 100644 index 0000000..1235960 --- /dev/null +++ b/src/network/access/qnetworkcookie.cpp @@ -0,0 +1,932 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkcookie.h" +#include "qnetworkcookie_p.h" + +#include "qnetworkrequest.h" +#include "qnetworkreply.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qdebug.h" +#include "QtCore/qlist.h" +#include "QtCore/qlocale.h" +#include "QtCore/qstring.h" +#include "QtCore/qurl.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QNetworkCookie + \since 4.4 + \brief The QNetworkCookie class holds one network cookie. + + Cookies are small bits of information that stateless protocols + like HTTP use to maintain some persistent information across + requests. + + A cookie is set by a remote server when it replies to a request + and it expects the same cookie to be sent back when further + requests are sent. + + QNetworkCookie holds one such cookie as received from the + network. A cookie has a name and a value, but those are opaque to + the application (that is, the information stored in them has no + meaning to the application). A cookie has an associated path name + and domain, which indicate when the cookie should be sent again to + the server. + + A cookie can also have an expiration date, indicating its + validity. If the expiration date is not present, the cookie is + considered a "session cookie" and should be discarded when the + application exits (or when its concept of session is over). + + QNetworkCookie provides a way of parsing a cookie from the HTTP + header format using the QNetworkCookie::parseCookies() + function. However, when received in a QNetworkReply, the cookie is + already parsed. + + This class implements cookies as described by the + \l{Netscape Cookie Specification}{initial cookie specification by + Netscape}, which is somewhat similar to the \l{RFC 2109} specification, + plus the \l{Mitigating Cross-site Scripting With HTTP-only Cookies} + {"HttpOnly" extension}. The more recent \l{RFC 2965} specification + (which uses the Set-Cookie2 header) is not supported. + + \sa QNetworkCookieJar, QNetworkRequest, QNetworkReply +*/ + +/*! + Create a new QNetworkCookie object, initializing the cookie name + to \a name and its value to \a value. + + A cookie is only valid if it has a name. However, the value is + opaque to the application and being empty may have significance to + the remote server. +*/ +QNetworkCookie::QNetworkCookie(const QByteArray &name, const QByteArray &value) + : d(new QNetworkCookiePrivate) +{ + qRegisterMetaType<QNetworkCookie>(); + qRegisterMetaType<QList<QNetworkCookie> >(); + + d->name = name; + d->value = value; +} + +/*! + Creates a new QNetworkCookie object by copying the contents of \a + other. +*/ +QNetworkCookie::QNetworkCookie(const QNetworkCookie &other) + : d(other.d) +{ +} + +/*! + Destroys this QNetworkCookie object. +*/ +QNetworkCookie::~QNetworkCookie() +{ + // QSharedDataPointer auto deletes + d = 0; +} + +/*! + Copies the contents of the QNetworkCookie object \a other to this + object. +*/ +QNetworkCookie &QNetworkCookie::operator=(const QNetworkCookie &other) +{ + d = other.d; + return *this; +} + +/*! + \fn bool QNetworkCookie::operator!=(const QNetworkCookie &other) const + + Returns true if this cookie is not equal to \a other. + + \sa operator==() +*/ + +/*! + Returns true if this cookie is equal to \a other. This function + only returns true if all fields of the cookie are the same. + + However, in some contexts, two cookies of the same name could be + considered equal. + + \sa operator!=() +*/ +bool QNetworkCookie::operator==(const QNetworkCookie &other) const +{ + if (d == other.d) + return true; + return d->name == other.d->name && + d->value == other.d->value && + d->expirationDate == other.d->expirationDate && + d->domain == other.d->domain && + d->path == other.d->path && + d->secure == other.d->secure && + d->comment == other.d->comment; +} + +/*! + Returns true if the "secure" option was specified in the cookie + string, false otherwise. + + Secure cookies may contain private information and should not be + resent over unencrypted connections. + + \sa setSecure() +*/ +bool QNetworkCookie::isSecure() const +{ + return d->secure; +} + +/*! + Sets the secure flag of this cookie to \a enable. + + Secure cookies may contain private information and should not be + resent over unencrypted connections. + + \sa isSecure() +*/ +void QNetworkCookie::setSecure(bool enable) +{ + d->secure = enable; +} + +/*! + \since 4.5 + + Returns true if the "HttpOnly" flag is enabled for this cookie. + + A cookie that is "HttpOnly" is only set and retrieved by the + network requests and replies; i.e., the HTTP protocol. It is not + accessible from scripts running on browsers. + + \sa isSecure() +*/ +bool QNetworkCookie::isHttpOnly() const +{ + return d->httpOnly; +} + +/*! + \since 4.5 + + Sets this cookie's "HttpOnly" flag to \a enable. +*/ +void QNetworkCookie::setHttpOnly(bool enable) +{ + d->httpOnly = enable; +} + +/*! + Returns true if this cookie is a session cookie. A session cookie + is a cookie which has no expiration date, which means it should be + discarded when the application's concept of session is over + (usually, when the application exits). + + \sa expirationDate(), setExpirationDate() +*/ +bool QNetworkCookie::isSessionCookie() const +{ + return !d->expirationDate.isValid(); +} + +/*! + Returns the expiration date for this cookie. If this cookie is a + session cookie, the QDateTime returned will not be valid. If the + date is in the past, this cookie has already expired and should + not be sent again back to a remote server. + + The expiration date corresponds to the parameters of the "expires" + entry in the cookie string. + + \sa isSessionCookie(), setExpirationDate() +*/ +QDateTime QNetworkCookie::expirationDate() const +{ + return d->expirationDate; +} + +/*! + Sets the expiration date of this cookie to \a date. Setting an + invalid expiration date to this cookie will mean it's a session + cookie. + + \sa isSessionCookie(), expirationDate() +*/ +void QNetworkCookie::setExpirationDate(const QDateTime &date) +{ + d->expirationDate = date; +} + +/*! + Returns the domain this cookie is associated with. This + corresponds to the "domain" field of the cookie string. + + Note that the domain here may start with a dot, which is not a + valid hostname. However, it means this cookie matches all + hostnames ending with that domain name. + + \sa setDomain() +*/ +QString QNetworkCookie::domain() const +{ + return d->domain; +} + +/*! + Sets the domain associated with this cookie to be \a domain. + + \sa domain() +*/ +void QNetworkCookie::setDomain(const QString &domain) +{ + d->domain = domain; +} + +/*! + Returns the path associated with this cookie. This corresponds to + the "path" field of the cookie string. + + \sa setPath() +*/ +QString QNetworkCookie::path() const +{ + return d->path; +} + +/*! + Sets the path associated with this cookie to be \a path. + + \sa path() +*/ +void QNetworkCookie::setPath(const QString &path) +{ + d->path = path; +} + +/*! + Returns the name of this cookie. The only mandatory field of a + cookie is its name, without which it is not considered valid. + + \sa setName(), value() +*/ +QByteArray QNetworkCookie::name() const +{ + return d->name; +} + +/*! + Sets the name of this cookie to be \a cookieName. Note that + setting a cookie name to an empty QByteArray will make this cookie + invalid. + + \sa name(), value() +*/ +void QNetworkCookie::setName(const QByteArray &cookieName) +{ + d->name = cookieName; +} + +/*! + Returns this cookies value, as specified in the cookie + string. Note that a cookie is still valid if its value is empty. + + Cookie name-value pairs are considered opaque to the application: + that is, their values don't mean anything. + + \sa setValue(), name() +*/ +QByteArray QNetworkCookie::value() const +{ + return d->value; +} + +/*! + Sets the value of this cookie to be \a value. + + \sa value(), name() +*/ +void QNetworkCookie::setValue(const QByteArray &value) +{ + d->value = value; +} + +// ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend +static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position) +{ + // format is one of: + // (1) token + // (2) token = token + // (3) token = quoted-string + int i; + const int length = text.length(); + position = nextNonWhitespace(text, position); + + // parse the first part, before the equal sign + for (i = position; i < length; ++i) { + register char c = text.at(i); + if (c == ';' || c == ',' || c == '=') + break; + } + + QByteArray first = text.mid(position, i - position).trimmed(); + position = i; + + if (first.isEmpty()) + return qMakePair(QByteArray(), QByteArray()); + if (i == length || text.at(i) != '=') + // no equal sign, we found format (1) + return qMakePair(first, QByteArray()); + + QByteArray second; + second.reserve(32); // arbitrary but works for most cases + + i = nextNonWhitespace(text, position + 1); + if (i < length && text.at(i) == '"') { + // a quote, we found format (3), where: + // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + // qdtext = <any TEXT except <">> + // quoted-pair = "\" CHAR + ++i; + while (i < length) { + register char c = text.at(i); + if (c == '"') { + // end of quoted text + break; + } else if (c == '\\') { + ++i; + if (i >= length) + // broken line + return qMakePair(QByteArray(), QByteArray()); + c = text.at(i); + } + + second += c; + ++i; + } + + for ( ; i < length; ++i) { + register char c = text.at(i); + if (c == ',' || c == ';') + break; + } + position = i; + } else { + // no quote, we found format (2) + position = i; + for ( ; i < length; ++i) { + register char c = text.at(i); + if (c == ',' || c == ';' || isLWS(c)) + break; + } + + second = text.mid(position, i - position).trimmed(); + position = i; + } + + if (second.isNull()) + second.resize(0); // turns into empty-but-not-null + return qMakePair(first, second); +} + +/*! + \enum QNetworkCookie::RawForm + + This enum is used with the toRawForm() function to declare which + form of a cookie shall be returned. + + \value NameAndValueOnly makes toRawForm() return only the + "NAME=VALUE" part of the cookie, as suitable for sending back + to a server in a client request's "Cookie:" header. Multiple + cookies are separated by a semi-colon in the "Cookie:" header + field. + + \value Full makes toRawForm() return the full + cookie contents, as suitable for sending to a client in a + server's "Set-Cookie:" header. Multiple cookies are separated + by commas in a "Set-Cookie:" header. + + Note that only the Full form of the cookie can be parsed back into + its original contents. + + \sa toRawForm(), parseCookies() +*/ + +/*! + Returns the raw form of this QNetworkCookie. The QByteArray + returned by this function is suitable for an HTTP header, either + in a server response (the Set-Cookie header) or the client request + (the Cookie header). You can choose from one of two formats, using + \a form. + + \sa parseCookies() +*/ +QByteArray QNetworkCookie::toRawForm(RawForm form) const +{ + QByteArray result; + if (d->name.isEmpty()) + return result; // not a valid cookie + + result = d->name; + result += '='; + if (d->value.contains(';') || + d->value.contains(',') || + d->value.contains(' ') || + d->value.contains('"')) { + result += '"'; + + QByteArray value = d->value; + value.replace('"', "\\\""); + result += value; + + result += '"'; + } else { + result += d->value; + } + + if (form == Full) { + // same as above, but encoding everything back + if (isSecure()) + result += "; secure"; + if (isHttpOnly()) + result += "; HttpOnly"; + if (!isSessionCookie()) { + result += "; expires="; + result += QLocale::c().toString(d->expirationDate.toUTC(), + QLatin1String("ddd, dd-MMM-yyyy hh:mm:ss 'GMT")).toLatin1(); + } + if (!d->domain.isEmpty()) { + result += "; domain="; + result += QUrl::toAce(d->domain); + } + if (!d->path.isEmpty()) { + result += "; path="; + result += QUrl::toPercentEncoding(d->path, "/"); + } + } + return result; +} + +/*! + Parses the cookie string \a cookieString as received from a server + response in the "Set-Cookie:" header. If there's a parsing error, + this function returns an empty list. + + Since the HTTP header can set more than one cookie at the same + time, this function returns a QList<QNetworkCookie>, one for each + cookie that is parsed. + + \sa toRawForm() +*/ +QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString) +{ + // According to http://wp.netscape.com/newsref/std/cookie_spec.html,< + // the Set-Cookie response header is of the format: + // + // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure + // + // where only the NAME=VALUE part is mandatory + // + // We do not support RFC 2965 Set-Cookie2-style cookies + + QList<QNetworkCookie> result; + QDateTime now = QDateTime::currentDateTime().toUTC(); + + int position = 0; + const int length = cookieString.length(); + while (position < length) { + QNetworkCookie cookie; + + // The first part is always the "NAME=VALUE" part + QPair<QByteArray,QByteArray> field = nextField(cookieString, position); + if (field.first.isEmpty() || field.second.isNull()) + // parsing error + return QList<QNetworkCookie>(); + cookie.setName(field.first); + cookie.setValue(field.second); + + position = nextNonWhitespace(cookieString, position); + bool endOfCookie = false; + while (!endOfCookie && position < length) + switch (cookieString.at(position++)) { + case ',': + // end of the cookie + endOfCookie = true; + break; + + case ';': + // new field in the cookie + field = nextField(cookieString, position); + field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive + + if (field.first == "expires") { + static const char dateFormats[] = + "d-MMM-yyyy hh:mm:ss\0" + "d MMM yyyy hh:mm:ss\0" + "d-MMM-yy hh:mm:ss\0" + "\0"; + + // expires is a special case because it contains a naked comma + // and naked spaces. The format is: + // expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT + // but we also accept standard HTTP dates + + // make sure we're at the comma + if (position >= length || cookieString.at(position) != ',') + // invalid cookie string + return QList<QNetworkCookie>(); + + ++position; + int end; + for (end = position; end < length; ++end) + if (cookieString.at(end) == ',' || cookieString.at(end) == ';') + break; + + QByteArray datestring = cookieString.mid(position, end - position).trimmed(); + position = end; + if (datestring.endsWith(" GMT") || datestring.endsWith(" UTC")) + datestring.chop(4); + else if (datestring.endsWith(" +0000")) + datestring.chop(6); + + size_t i = 0; + int j = 0; + QLocale cLocale = QLocale::c(); + QDateTime dt; + do { + QLatin1String df(dateFormats + i); + i += strlen(dateFormats + i) + 1; + +#ifndef QT_NO_DATESTRING + dt = cLocale.toDateTime(QString::fromLatin1(datestring), df); + + // some cookies are set with a two-digit year + // (although this is not allowed); this is interpreted as a year + // in the 20th century by QDateTime. + // Work around this case here (assuming 00-69 is 21st century, + // 70-99 is 20th century) + QDate date = dt.date(); + if (j == 2 && date.year() >= 1900 && date.year() < 1970) + dt = dt.addYears(100); + if (date.year() >= 0 && date.year() < 100) + dt = dt.addYears(1900); +#endif + j++; + } while (!dt.isValid() && i <= sizeof dateFormats - 1); + if (!dt.isValid()) + // invalid cookie string + return QList<QNetworkCookie>(); + + dt.setTimeSpec(Qt::UTC); + cookie.setExpirationDate(dt); + } else if (field.first == "domain") { + QByteArray rawDomain = field.second; + QString maybeLeadingDot; + if (rawDomain.startsWith('.')) { + maybeLeadingDot = QLatin1Char('.'); + rawDomain = rawDomain.mid(1); + } + + QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain))); + cookie.setDomain(maybeLeadingDot + normalizedDomain); + } else if (field.first == "max-age") { + bool ok = false; + int secs = field.second.toInt(&ok); + if (!ok) + // invalid cookie string + return QList<QNetworkCookie>(); + cookie.setExpirationDate(now.addSecs(secs)); + } else if (field.first == "path") { + QString path = QUrl::fromPercentEncoding(field.second); + cookie.setPath(path); + } else if (field.first == "secure") { + cookie.setSecure(true); + } else if (field.first == "httponly") { + cookie.setHttpOnly(true); + } else if (field.first == "comment") { + //cookie.setComment(QString::fromUtf8(field.second)); + } else if (field.first == "version") { + if (field.second != "1") { + // oops, we don't know how to handle this cookie + cookie = QNetworkCookie(); + endOfCookie = true; + continue; + } + } else { + // got an unknown field in the cookie + // what do we do? + } + + position = nextNonWhitespace(cookieString, position); + } + + result += cookie; + } + + return result; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug s, const QNetworkCookie &cookie) +{ + s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ")"; + return s.space(); +} +#endif + + + +class QNetworkCookieJarPrivate: public QObjectPrivate +{ +public: + QList<QNetworkCookie> allCookies; + + Q_DECLARE_PUBLIC(QNetworkCookieJar) +}; + +/*! + \class QNetworkCookieJar + \brief The QNetworkCookieJar class implements a simple jar of QNetworkCookie objects + \since 4.4 + + Cookies are small bits of information that stateless protocols + like HTTP use to maintain some persistent information across + requests. + + A cookie is set by a remote server when it replies to a request + and it expects the same cookie to be sent back when further + requests are sent. + + The cookie jar is the object that holds all cookies set in + previous requests. Web browsers save their cookie jars to disk in + order to conserve permanent cookies across invocations of the + application. + + QNetworkCookieJar does not implement permanent storage: it only + keeps the cookies in memory. Once the QNetworkCookieJar object is + deleted, all cookies it held will be discarded as well. If you + want to save the cookies, you should derive from this class and + implement the saving to disk to your own storage format. + + This class implements only the basic security recommended by the + cookie specifications and does not implement any cookie acceptance + policy (it accepts all cookies set by any requests). In order to + override those rules, you should reimplement the + cookiesForUrl() and setCookiesFromUrl() virtual + functions. They are called by QNetworkReply and + QNetworkAccessManager when they detect new cookies and when they + require cookies. + + \sa QNetworkCookie, QNetworkAccessManager, QNetworkReply, + QNetworkRequest, QNetworkAccessManager::setCookieJar() +*/ + +/*! + Creates a QNetworkCookieJar object and sets the parent object to + be \a parent. + + The cookie jar is initialized to empty. +*/ +QNetworkCookieJar::QNetworkCookieJar(QObject *parent) + : QObject(*new QNetworkCookieJarPrivate, parent) +{ +} + +/*! + Destroys this cookie jar object and discards all cookies stored in + it. Cookies are not saved to disk in the QNetworkCookieJar default + implementation. + + If you need to save the cookies to disk, you have to derive from + QNetworkCookieJar and save the cookies to disk yourself. +*/ +QNetworkCookieJar::~QNetworkCookieJar() +{ +} + +/*! + Returns all cookies stored in this cookie jar. This function is + suitable for derived classes to save cookies to disk, as well as + to implement cookie expiration and other policies. + + \sa setAllCookies(), cookiesForUrl() +*/ +QList<QNetworkCookie> QNetworkCookieJar::allCookies() const +{ + return d_func()->allCookies; +} + +/*! + Sets the internal list of cookies held by this cookie jar to be \a + cookieList. This function is suitable for derived classes to + implement loading cookies from permanent storage, or their own + cookie acceptance policies by reimplementing + setCookiesFromUrl(). + + \sa allCookies(), setCookiesFromUrl() +*/ +void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList) +{ + Q_D(QNetworkCookieJar); + d->allCookies = cookieList; +} + +static inline bool isParentPath(QString path, QString reference) +{ + if (!path.endsWith(QLatin1Char('/'))) + path += QLatin1Char('/'); + if (!reference.endsWith(QLatin1Char('/'))) + reference += QLatin1Char('/'); + return path.startsWith(reference); +} + +static inline bool isParentDomain(QString domain, QString reference) +{ + if (!reference.startsWith(QLatin1Char('.'))) + return domain == reference; + + return domain.endsWith(reference) || domain == reference.mid(1); +} + +/*! + Adds the cookies in the list \a cookieList to this cookie + jar. Default values for path and domain are taken from the \a + url object. + + Returns true if one or more cookes are set for url otherwise false. + + If a cookie already exists in the cookie jar, it will be + overridden by those in \a cookieList. + + The default QNetworkCookieJar class implements only a very basic + security policy (it makes sure that the cookies' domain and path + match the reply's). To enhance the security policy with your own + algorithms, override setCookiesFromUrl(). + + Also, QNetworkCookieJar does not have a maximum cookie jar + size. Reimplement this function to discard older cookies to create + room for new ones. + + \sa cookiesForUrl(), QNetworkAccessManager::setCookieJar() +*/ +bool QNetworkCookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, + const QUrl &url) +{ + Q_D(QNetworkCookieJar); + QString defaultDomain = url.host(); + QString pathAndFileName = url.path(); + QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(QLatin1Char('/'))+1); + if (defaultPath.isEmpty()) + defaultPath = QLatin1Char('/'); + + int added = 0; + QDateTime now = QDateTime::currentDateTime(); + foreach (QNetworkCookie cookie, cookieList) { + bool isDeletion = !cookie.isSessionCookie() && + cookie.expirationDate() < now; + + // validate the cookie & set the defaults if unset + // (RFC 2965: "The request-URI MUST path-match the Path attribute of the cookie.") + if (cookie.path().isEmpty()) + cookie.setPath(defaultPath); + else if (!isParentPath(pathAndFileName, cookie.path())) + continue; // not accepted + + if (cookie.domain().isEmpty()) { + cookie.setDomain(defaultDomain); + } else { + QString domain = cookie.domain(); + if (!(isParentDomain(domain, defaultDomain) + || isParentDomain(defaultDomain, domain))) { + continue; // not accepted + } + } + + QList<QNetworkCookie>::Iterator it = d->allCookies.begin(), + end = d->allCookies.end(); + for ( ; it != end; ++it) + // does this cookie already exist? + if (cookie.name() == it->name() && + cookie.domain() == it->domain() && + cookie.path() == it->path()) { + // found a match + d->allCookies.erase(it); + break; + } + + // did not find a match + if (!isDeletion) { + d->allCookies += cookie; + ++added; + } + } + return (added > 0); +} + +/*! + Returns the cookies to be added to when a request is sent to + \a url. This function is called by the default + QNetworkAccessManager::createRequest(), which adds the + cookies returned by this function to the request being sent. + + If more than one cookie with the same name is found, but with + differing paths, the one with longer path is returned before the + one with shorter path. In other words, this function returns + cookies sorted by path length. + + The default QNetworkCookieJar class implements only a very basic + security policy (it makes sure that the cookies' domain and path + match the reply's). To enhance the security policy with your own + algorithms, override cookiesForUrl(). + + \sa setCookiesFromUrl(), QNetworkAccessManager::setCookieJar() +*/ +QList<QNetworkCookie> QNetworkCookieJar::cookiesForUrl(const QUrl &url) const +{ +// \b Warning! This is only a dumb implementation! +// It does NOT follow all of the recommendations from +// http://wp.netscape.com/newsref/std/cookie_spec.html +// It does not implement a very good cross-domain verification yet. + + Q_D(const QNetworkCookieJar); + QDateTime now = QDateTime::currentDateTime(); + QList<QNetworkCookie> result; + + // scan our cookies for something that matches + QList<QNetworkCookie>::ConstIterator it = d->allCookies.constBegin(), + end = d->allCookies.constEnd(); + for ( ; it != end; ++it) { + if (!isParentDomain(url.host(), it->domain())) + continue; + if (!isParentPath(url.path(), it->path())) + continue; + if (!(*it).isSessionCookie() && (*it).expirationDate() < now) + continue; + + // insert this cookie into result, sorted by path + QList<QNetworkCookie>::Iterator insertIt = result.begin(); + while (insertIt != result.end()) { + if (insertIt->path().length() < it->path().length()) { + // insert here + insertIt = result.insert(insertIt, *it); + break; + } else { + ++insertIt; + } + } + + // this is the shortest path yet, just append + if (insertIt == result.end()) + result += *it; + } + + return result; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkcookie.h b/src/network/access/qnetworkcookie.h new file mode 100644 index 0000000..e8dfab0 --- /dev/null +++ b/src/network/access/qnetworkcookie.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKCOOKIE_H +#define QNETWORKCOOKIE_H + +#include <QtCore/QSharedDataPointer> +#include <QtCore/QList> +#include <QtCore/QMetaType> +#include <QtCore/QObject> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QByteArray; +class QDateTime; +class QString; +class QUrl; + +class QNetworkCookiePrivate; +class Q_NETWORK_EXPORT QNetworkCookie +{ +public: + enum RawForm { + NameAndValueOnly, + Full + }; + + QNetworkCookie(const QByteArray &name = QByteArray(), const QByteArray &value = QByteArray()); + QNetworkCookie(const QNetworkCookie &other); + ~QNetworkCookie(); + QNetworkCookie &operator=(const QNetworkCookie &other); + bool operator==(const QNetworkCookie &other) const; + inline bool operator!=(const QNetworkCookie &other) const + { return !(*this == other); } + + bool isSecure() const; + void setSecure(bool enable); + bool isHttpOnly() const; + void setHttpOnly(bool enable); + + bool isSessionCookie() const; + QDateTime expirationDate() const; + void setExpirationDate(const QDateTime &date); + + QString domain() const; + void setDomain(const QString &domain); + + QString path() const; + void setPath(const QString &path); + + QByteArray name() const; + void setName(const QByteArray &cookieName); + + QByteArray value() const; + void setValue(const QByteArray &value); + + QByteArray toRawForm(RawForm form = Full) const; + + static QList<QNetworkCookie> parseCookies(const QByteArray &cookieString); + +private: + QSharedDataPointer<QNetworkCookiePrivate> d; + friend class QNetworkCookiePrivate; +}; +Q_DECLARE_TYPEINFO(QNetworkCookie, Q_MOVABLE_TYPE); + +class QNetworkCookieJarPrivate; +class Q_NETWORK_EXPORT QNetworkCookieJar: public QObject +{ + Q_OBJECT +public: + QNetworkCookieJar(QObject *parent = 0); + virtual ~QNetworkCookieJar(); + + virtual QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const; + virtual bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url); + +protected: + QList<QNetworkCookie> allCookies() const; + void setAllCookies(const QList<QNetworkCookie> &cookieList); + +private: + Q_DECLARE_PRIVATE(QNetworkCookieJar) + Q_DISABLE_COPY(QNetworkCookieJar) +}; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug, const QNetworkCookie &); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QNetworkCookie) +Q_DECLARE_METATYPE(QList<QNetworkCookie>) + +QT_END_HEADER + +#endif diff --git a/src/network/access/qnetworkcookie_p.h b/src/network/access/qnetworkcookie_p.h new file mode 100644 index 0000000..83ef14a --- /dev/null +++ b/src/network/access/qnetworkcookie_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKCOOKIE_P_H +#define QNETWORKCOOKIE_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 framework. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qdatetime.h" + +QT_BEGIN_NAMESPACE + +class QNetworkCookiePrivate: public QSharedData +{ +public: + inline QNetworkCookiePrivate() : secure(false), httpOnly(false) { } + + QDateTime expirationDate; + QString domain; + QString path; + QString comment; + QByteArray name; + QByteArray value; + bool secure; + bool httpOnly; +}; + +static inline bool isLWS(register char c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static int nextNonWhitespace(const QByteArray &text, int from) +{ + // RFC 2616 defines linear whitespace as: + // LWS = [CRLF] 1*( SP | HT ) + // We ignore the fact that CRLF must come as a pair at this point + // It's an invalid HTTP header if that happens. + while (from < text.length()) { + if (isLWS(text.at(from))) + ++from; + else + return from; // non-whitespace + } + + // reached the end + return text.length(); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp new file mode 100644 index 0000000..fa0fccb --- /dev/null +++ b/src/network/access/qnetworkdiskcache.cpp @@ -0,0 +1,666 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNETWORKDISKCACHE_DEBUG + +#include "qnetworkdiskcache.h" +#include "qnetworkdiskcache_p.h" + +#include <qfile.h> +#include <qdir.h> +#include <qdatetime.h> +#include <qdiriterator.h> +#include <qcryptographichash.h> +#include <qurl.h> + +#include <qdebug.h> + +#define CACHE_PREFIX QLatin1String("cache_") +#define CACHE_POSTFIX QLatin1String(".cache") +#define MAX_COMPRESSION_SIZE (1024 * 1024 * 3) + +QT_BEGIN_NAMESPACE + +/*! + \class QNetworkDiskCache + \since 4.5 + \inmodule QtNetwork + + \brief The QNetworkDiskCache class provides a very basic disk cache. + + QNetworkDiskCache stores each url in its own file inside of the + cacheDirectory using QDataStream. Files with a text MimeType + are compressed using qCompress. Each cache file starts with "cache_" + and ends in ".cache". Data is written to disk only in insert() + and updateMetaData(). + + Currently you can not share the same cache files with more then + one disk cache. + + QNetworkDiskCache by default limits the amount of space that the cache will + use on the system to 50MB. + + Note you have to set the cache directory before it will work. +*/ + +/*! + Creates a new disk cache. The \a parent argument is passed to + QAbstractNetworkCache's constructor. + */ +QNetworkDiskCache::QNetworkDiskCache(QObject *parent) + : QAbstractNetworkCache(*new QNetworkDiskCachePrivate, parent) +{ +} + +/*! + Destroys the cache object. This does not clear the disk cache. + */ +QNetworkDiskCache::~QNetworkDiskCache() +{ + Q_D(QNetworkDiskCache); + QHashIterator<QIODevice*, QCacheItem*> it(d->inserting); + while (it.hasNext()) { + it.next(); + delete it.value(); + } +} + +/*! + Returns the location where cached files will be stored. +*/ +QString QNetworkDiskCache::cacheDirectory() const +{ + Q_D(const QNetworkDiskCache); + return d->cacheDirectory; +} + +/*! + Sets the directory where cached files will be stored to \a cacheDir + + QNetworkDiskCache will create this directory if it does not exists. + + Prepared cache items will be stored in the new cache directory when + they are inserted. + + \sa QDesktopServices::CacheLocation +*/ +void QNetworkDiskCache::setCacheDirectory(const QString &cacheDir) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::setCacheDirectory()" << cacheDir; +#endif + Q_D(QNetworkDiskCache); + if (cacheDir.isEmpty()) + return; + d->cacheDirectory = cacheDir; + QDir dir(d->cacheDirectory); + d->cacheDirectory = dir.absolutePath(); + if (!d->cacheDirectory.endsWith(QLatin1Char('/'))) + d->cacheDirectory += QLatin1Char('/'); +} + +/*! + \reimp +*/ +qint64 QNetworkDiskCache::cacheSize() const +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::cacheSize()"; +#endif + Q_D(const QNetworkDiskCache); + if (d->cacheDirectory.isEmpty()) + return 0; + if (d->currentCacheSize < 0) { + QNetworkDiskCache *that = const_cast<QNetworkDiskCache*>(this); + that->d_func()->currentCacheSize = that->expire(); + } + return d->currentCacheSize; +} + +/*! + \reimp +*/ +QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::prepare()" << metaData.url(); +#endif + Q_D(QNetworkDiskCache); + if (!metaData.isValid() || !metaData.url().isValid() || !metaData.saveToDisk()) + return 0; + + if (d->cacheDirectory.isEmpty()) { + qWarning() << "QNetworkDiskCache::prepare() The cache directory is not set"; + return 0; + } + + foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) { + if (header.first.toLower() == "content-length") { + qint64 size = header.second.toInt(); + if (size > (maximumCacheSize() * 3)/4) + return 0; + break; + } + } + + QCacheItem *cacheItem = new QCacheItem; + cacheItem->metaData = metaData; + + QIODevice *device = 0; + if (cacheItem->canCompress()) { + cacheItem->data.open(QBuffer::ReadWrite); + device = &(cacheItem->data); + } else { + QString templateName = d->tmpCacheFileName(); + cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data); + cacheItem->file->open(); + cacheItem->writeHeader(cacheItem->file); + device = cacheItem->file; + } + d->inserting[device] = cacheItem; + return device; +} + +/*! + \reimp +*/ +void QNetworkDiskCache::insert(QIODevice *device) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::insert()" << device; +#endif + Q_D(QNetworkDiskCache); + QHash<QIODevice*, QCacheItem*>::iterator it = d->inserting.find(device); + if (it == d->inserting.end()) { + qWarning() << "QNetworkDiskCache::insert() called on a device we don't know about" << device; + return; + } + + d->storeItem(it.value()); + delete it.value(); + d->inserting.erase(it); +} + +void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem) +{ + Q_Q(QNetworkDiskCache); + Q_ASSERT(cacheItem->metaData.saveToDisk()); + + QString fileName = cacheFileName(cacheItem->metaData.url()); + Q_ASSERT(!fileName.isEmpty()); + + if (QFile::exists(fileName)) { + if (!QFile::remove(fileName)) { + qWarning() << "QNetworkDiskCache: could't remove the cache file " << fileName; + return; + } + } + + if (currentCacheSize > 0) + currentCacheSize += 1024 + cacheItem->size(); + currentCacheSize = q->expire(); + if (!cacheItem->file) { + QString templateName = tmpCacheFileName(); + cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data); + if (cacheItem->file->open()) { + cacheItem->writeHeader(cacheItem->file); + cacheItem->writeCompressedData(cacheItem->file); + } + } + + if (cacheItem->file + && cacheItem->file->isOpen() + && cacheItem->file->error() == QFile::NoError) { + cacheItem->file->setAutoRemove(false); + // ### use atomic rename rather then remove & rename + if (cacheItem->file->rename(fileName)) + currentCacheSize += cacheItem->file->size(); + cacheItem->file->setAutoRemove(true); + } + if (cacheItem->metaData.url() == lastItem.metaData.url()) + lastItem.reset(); +} + +/*! + \reimp +*/ +bool QNetworkDiskCache::remove(const QUrl &url) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::remove()" << url; +#endif + Q_D(QNetworkDiskCache); + + // remove is also used to cancel insertions, not a common operation + QHashIterator<QIODevice*, QCacheItem*> it(d->inserting); + while (it.hasNext()) { + it.next(); + QCacheItem *item = it.value(); + if (item && item->metaData.url() == url) { + delete item; + d->inserting.remove(it.key()); + return true; + } + } + + if (d->lastItem.metaData.url() == url) + d->lastItem.reset(); + return d->removeFile(d->cacheFileName(url)); +} + +/*! + Put all of the misc file removing into one function to be extra safe + */ +bool QNetworkDiskCachePrivate::removeFile(const QString &file) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::removFile()" << file; +#endif + if (file.isEmpty()) + return false; + QFileInfo info(file); + QString fileName = info.fileName(); + if (!fileName.endsWith(CACHE_POSTFIX) || !fileName.startsWith(CACHE_PREFIX)) + return false; + qint64 size = info.size(); + if (QFile::remove(file)) { + currentCacheSize -= size; + return true; + } + return false; +} + +/*! + \reimp +*/ +QNetworkCacheMetaData QNetworkDiskCache::metaData(const QUrl &url) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::metaData()" << url; +#endif + Q_D(QNetworkDiskCache); + if (d->lastItem.metaData.url() == url) + return d->lastItem.metaData; + return fileMetaData(d->cacheFileName(url)); +} + +/*! + Returns the QNetworkCacheMetaData for the cache file \a fileName. + + If \a fileName is not a cache file QNetworkCacheMetaData will be invalid. + */ +QNetworkCacheMetaData QNetworkDiskCache::fileMetaData(const QString &fileName) const +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::fileMetaData()" << fileName; +#endif + Q_D(const QNetworkDiskCache); + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) + return QNetworkCacheMetaData(); + if (!d->lastItem.read(&file, false)) { + file.close(); + QNetworkDiskCachePrivate *that = const_cast<QNetworkDiskCachePrivate*>(d); + that->removeFile(fileName); + } + return d->lastItem.metaData; +} + +/*! + \reimp +*/ +QIODevice *QNetworkDiskCache::data(const QUrl &url) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::data()" << url; +#endif + Q_D(QNetworkDiskCache); + QBuffer *buffer = 0; + if (!url.isValid()) + return buffer; + if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) { + buffer = new QBuffer; + buffer->setData(d->lastItem.data.data()); + } else { + QFile *file = new QFile(d->cacheFileName(url)); + if (!file->open(QFile::ReadOnly | QIODevice::Unbuffered)) { + delete file; + return 0; + } + if (!d->lastItem.read(file, true)) { + file->close(); + remove(url); + delete file; + return 0; + } + if (d->lastItem.data.isOpen()) { + // compressed + buffer = new QBuffer; + buffer->setData(d->lastItem.data.data()); + delete file; + } else { + buffer = new QBuffer; + // ### verify that QFile uses the fd size and not the file name + qint64 size = file->size() - file->pos(); + const uchar *p = 0; +#ifndef Q_OS_WINCE + p = file->map(file->pos(), size); +#endif + if (p) { + file->setParent(buffer); + buffer->setData((const char *)p, size); + } else { + buffer->setData(file->readAll()); + delete file; + } + } + } + buffer->open(QBuffer::ReadOnly); + return buffer; +} + +/*! + \reimp +*/ +void QNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::updateMetaData()" << metaData.url(); +#endif + QUrl url = metaData.url(); + QIODevice *oldDevice = data(url); + if (!oldDevice) { +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::updateMetaData(), no device!"; +#endif + return; + } + + QIODevice *newDevice = prepare(metaData); + if (!newDevice) { +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::updateMetaData(), no new device!" << url; +#endif + return; + } + char data[1024]; + while (!oldDevice->atEnd()) { + qint64 s = oldDevice->read(data, 1024); + newDevice->write(data, s); + } + delete oldDevice; + insert(newDevice); +} + +/*! + Returns the current maximum size for the disk cache. + + \sa setMaximumCacheSize() + */ +qint64 QNetworkDiskCache::maximumCacheSize() const +{ + Q_D(const QNetworkDiskCache); + return d->maximumCacheSize; +} + +/*! + Sets the maximum size of the disk cache to be \a size. + + If the new size is smaller then the current cache size then the cache will call expire(). + + \sa maximumCacheSize() + */ +void QNetworkDiskCache::setMaximumCacheSize(qint64 size) +{ + Q_D(QNetworkDiskCache); + bool expireCache = (size < d->maximumCacheSize); + d->maximumCacheSize = size; + if (expireCache) + d->currentCacheSize = expire(); +} + +/*! + Cleans the cache so that its size is under the maximum cache size. + Returns the current size of the cache. + + When the current size of the cache is greater then the maximumCacheSize() + older cache files are removed until the total size is less then 90% of + maximumCacheSize() starting with the oldest ones first using the file + creation date to determine how old a cache file is. + + Subclasses can reimplement this function to change the order that cache + files are removed taking into account information in the application + knows about that QNetworkDiskCache does not, for example the number of times + a cache is accessed. + + Note: cacheSize() calls expire if the current cache size is unknown. + + \sa maximumCacheSize(), fileMetaData() + */ +qint64 QNetworkDiskCache::expire() +{ + Q_D(QNetworkDiskCache); + if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize()) + return d->currentCacheSize; + + if (cacheDirectory().isEmpty()) { + qWarning() << "QNetworkDiskCache::expire() The cache directory is not set"; + return 0; + } + + QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot; + QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories); + + QMap<QDateTime, QString> cacheItems; + qint64 totalSize = 0; + while (it.hasNext()) { + QString path = it.next(); + QFileInfo info = it.fileInfo(); + QString fileName = info.fileName(); + if (fileName.endsWith(CACHE_POSTFIX) && fileName.startsWith(CACHE_PREFIX)) { + cacheItems[info.created()] = path; + totalSize += info.size(); + } + } + + int removedFiles = 0; + qint64 goal = (maximumCacheSize() * 9) / 10; + QMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin(); + while (i != cacheItems.constEnd()) { + if (totalSize < goal) + break; + QString name = i.value(); + QFile file(name); + qint64 size = file.size(); + file.remove(); + totalSize -= size; + ++removedFiles; + ++i; + } +#if defined(QNETWORKDISKCACHE_DEBUG) + if (removedFiles > 0) { + qDebug() << "QNetworkDiskCache::expire()" + << "Removed:" << removedFiles + << "Kept:" << cacheItems.count() - removedFiles; + } +#endif + if (removedFiles > 0) + d->lastItem.reset(); + return totalSize; +} + +/*! + \reimp +*/ +void QNetworkDiskCache::clear() +{ +#if defined(QNETWORKDISKCACHE_DEBUG) + qDebug() << "QNetworkDiskCache::clear()"; +#endif + Q_D(QNetworkDiskCache); + qint64 size = d->maximumCacheSize; + d->maximumCacheSize = 0; + d->currentCacheSize = expire(); + d->maximumCacheSize = size; +} + +QByteArray QNetworkDiskCachePrivate::generateId(const QUrl &url) const +{ + QUrl cleanUrl = url; + cleanUrl.setPassword(QString()); + cleanUrl.setFragment(QString()); + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(cleanUrl.toEncoded()); + return hash.result().toHex(); +} + +QString QNetworkDiskCachePrivate::tmpCacheFileName() const +{ + QDir dir; + dir.mkpath(cacheDirectory + QLatin1String("prepared/")); + return cacheDirectory + QLatin1String("prepared/") + CACHE_PREFIX + QLatin1String("XXXXXX") + CACHE_POSTFIX; +} + +QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const +{ + if (!url.isValid()) + return QString(); + QString directory = cacheDirectory + url.scheme() + QLatin1Char('/'); + if (!QFile::exists(directory)) { + // ### make a static QDir function for this... + QDir dir; + dir.mkpath(directory); + } + + QString fileName = CACHE_PREFIX + QLatin1String(generateId(url)) + CACHE_POSTFIX; + return directory + fileName; +} + +/*! + We compress small text and JavaScript files. + */ +bool QCacheItem::canCompress() const +{ + bool sizeOk = false; + bool typeOk = false; + foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) { + if (header.first.toLower() == "content-length") { + qint64 size = header.second.toLongLong(); + if (size > MAX_COMPRESSION_SIZE) + return false; + else + sizeOk = true; + } + + if (header.first.toLower() == "content-type") { + QByteArray type = header.second; + if (type.startsWith("text/") + || (type.startsWith("application/") + && (type.endsWith("javascript") || type.endsWith("ecmascript")))) + typeOk = true; + else + return false; + } + if (sizeOk && typeOk) + return true; + } + return false; +} + +enum +{ + CacheMagic = 0xe8, + CurrentCacheVersion = 7 +}; + +void QCacheItem::writeHeader(QFile *device) const +{ + QDataStream out(device); + + out << qint32(CacheMagic); + out << qint32(CurrentCacheVersion); + out << metaData; + bool compressed = canCompress(); + out << compressed; +} + +void QCacheItem::writeCompressedData(QFile *device) const +{ + QDataStream out(device); + + out << qCompress(data.data()); +} + +/*! + Returns false if the file is a cache file, + but is an older version and should be removed otherwise true. + */ +bool QCacheItem::read(QFile *device, bool readData) +{ + reset(); + + QDataStream in(device); + + qint32 marker; + qint32 v; + in >> marker; + in >> v; + if (marker != CacheMagic) + return true; + + // If the cache magic is correct, but the version is not we should remove it + if (v != CurrentCacheVersion) + return false; + + bool compressed; + QByteArray dataBA; + in >> metaData; + in >> compressed; + if (readData && compressed) { + in >> dataBA; + data.setData(qUncompress(dataBA)); + data.open(QBuffer::ReadOnly); + } + return metaData.isValid(); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkdiskcache.h b/src/network/access/qnetworkdiskcache.h new file mode 100644 index 0000000..ca4bb94 --- /dev/null +++ b/src/network/access/qnetworkdiskcache.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKDISKCACHE_H +#define QNETWORKDISKCACHE_H + +#include <QtNetwork/qabstractnetworkcache.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QNetworkDiskCachePrivate; +class Q_NETWORK_EXPORT QNetworkDiskCache : public QAbstractNetworkCache +{ + Q_OBJECT + +public: + explicit QNetworkDiskCache(QObject *parent = 0); + ~QNetworkDiskCache(); + + QString cacheDirectory() const; + void setCacheDirectory(const QString &cacheDir); + + qint64 maximumCacheSize() const; + void setMaximumCacheSize(qint64 size); + + qint64 cacheSize() const; + QNetworkCacheMetaData metaData(const QUrl &url); + void updateMetaData(const QNetworkCacheMetaData &metaData); + QIODevice *data(const QUrl &url); + bool remove(const QUrl &url); + QIODevice *prepare(const QNetworkCacheMetaData &metaData); + void insert(QIODevice *device); + + QNetworkCacheMetaData fileMetaData(const QString &fileName) const; + +public Q_SLOTS: + void clear(); + +protected: + virtual qint64 expire(); + +private: + Q_DECLARE_PRIVATE(QNetworkDiskCache) + Q_DISABLE_COPY(QNetworkDiskCache) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QNETWORKDISKCACHE_H + diff --git a/src/network/access/qnetworkdiskcache_p.h b/src/network/access/qnetworkdiskcache_p.h new file mode 100644 index 0000000..bf6f37c --- /dev/null +++ b/src/network/access/qnetworkdiskcache_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKDISKCACHE_P_H +#define QNETWORKDISKCACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractnetworkcache_p.h" + +#include <qbuffer.h> +#include <qhash.h> +#include <qtemporaryfile.h> + +QT_BEGIN_NAMESPACE + +class QFile; + +class QCacheItem +{ +public: + QCacheItem() : file(0) + { + } + ~QCacheItem() + { + reset(); + } + + QNetworkCacheMetaData metaData; + QBuffer data; + QTemporaryFile *file; + inline qint64 size() const + { return file ? file->size() : data.size(); } + + inline void reset() { + metaData = QNetworkCacheMetaData(); + data.close(); + delete file; + file = 0; + } + void writeHeader(QFile *device) const; + void writeCompressedData(QFile *device) const; + bool read(QFile *device, bool readData); + + bool canCompress() const; +}; + +class QNetworkDiskCachePrivate : public QAbstractNetworkCachePrivate +{ +public: + QNetworkDiskCachePrivate() + : QAbstractNetworkCachePrivate() + , maximumCacheSize(1024 * 1024 * 50) + , currentCacheSize(-1) + {} + + QByteArray generateId(const QUrl &url) const; + QString cacheFileName(const QUrl &url) const; + QString tmpCacheFileName() const; + bool removeFile(const QString &file); + void storeItem(QCacheItem *item); + + mutable QCacheItem lastItem; + QString cacheDirectory; + qint64 maximumCacheSize; + qint64 currentCacheSize; + + QHash<QIODevice*, QCacheItem*> inserting; + Q_DECLARE_PUBLIC(QNetworkDiskCache) +}; + +QT_END_NAMESPACE + +#endif // QNETWORKDISKCACHE_P_H diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp new file mode 100644 index 0000000..f4dad3c --- /dev/null +++ b/src/network/access/qnetworkreply.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include <QtNetwork/qsslconfiguration.h> + +QT_BEGIN_NAMESPACE + +QNetworkReplyPrivate::QNetworkReplyPrivate() + : readBufferMaxSize(0), + operation(QNetworkAccessManager::UnknownOperation), + errorCode(QNetworkReply::NoError) +{ + // set the default attribute values + attributes.insert(QNetworkRequest::ConnectionEncryptedAttribute, false); +} + + +/*! + \class QNetworkReply + \since 4.4 + \brief The QNetworkReply class contains the data and headers for a request + posted with QNetworkAccessManager + + \reentrant + \inmodule QtNetwork + + The QNetworkReply class contains the data and meta data related to + a request posted with QNetworkAccessManager. Like QNetworkRequest, + it contains a URL and headers (both in parsed and raw form), some + information about the reply's state and the contents of the reply + itself. + + QNetworkReply is a sequential-access QIODevice, which means that + once data is read from the object, it no longer kept by the + device. It is therefore the application's responsibility to keep + this data if it needs to. Whenever more data is received from the + network and processed, the readyRead() signal is emitted. + + The downloadProgress() signal is also emitted when data is + received, but the number of bytes contained in it may not + represent the actual bytes received, if any transformation is done + to the contents (for example, decompressing and removing the + protocol overhead). + + Even though QNetworkReply is a QIODevice connected to the contents + of the reply, it also emits the uploadProgress() signal, which + indicates the progress of the upload for operations that have such + content. + + \sa QNetworkRequest, QNetworkAccessManager +*/ + +/*! + \enum QNetworkReply::NetworkError + + Indicates all possible error conditions found during the + processing of the request. + + \value NoError no error condition. + \note When the HTTP protocol returns a redirect no error will be + reported. You can check if there is a redirect with the + QNetworkRequest::RedirectionTargetAttribute attribute. + + \value ConnectionRefusedError the remote server refused the + connection (the server is not accepting requests) + + \value RemoteHostClosedError the remote server closed the + connection prematurely, before the entire reply was received and + processed + + \value HostNotFoundError the remote host name was not found + (invalid hostname) + + \value TimeoutError the connection to the remote server + timed out + + \value OperationCanceledError the operation was canceled via calls + to abort() or close() before it was finished. + + \value SslHandshakeFailedError the SSL/TLS handshake failed and the + encrypted channel could not be established. The sslErrors() signal + should have been emitted. + + \value ProxyConnectionRefusedError the connection to the proxy + server was refused (the proxy server is not accepting requests) + + \value ProxyConnectionClosedError the proxy server closed the + connection prematurely, before the entire reply was received and + processed + + \value ProxyNotFoundError the proxy host name was not + found (invalid proxy hostname) + + \value ProxyTimeoutError the connection to the proxy + timed out or the proxy did not reply in time to the request sent + + \value ProxyAuthenticationRequiredError the proxy requires + authentication in order to honour the request but did not accept + any credentials offered (if any) + + \value ContentAccessDenied the access to the remote + content was denied (similar to HTTP error 401) + + \value ContentOperationNotPermittedError the operation requested + on the remote content is not permitted + + \value ContentNotFoundError the remote content was not + found at the server (similar to HTTP error 404) + + \value AuthenticationRequiredError the remote server requires + authentication to serve the content but the credentials provided + were not accepted (if any) + + \value ProtocolUnknownError the Network Access API cannot + honor the request because the protocol is not known + + \value ProtocolInvalidOperationError the requested operation is + invalid for this protocol + + \value UnknownNetworkError an unknown network-related + error was detected + + \value UnknownProxyError an unknown proxy-related error + was detected + + \value UnknownContentError an unknonwn error related to + the remote content was detected + + \value ProtocolFailure a breakdown in protocol was + detected (parsing error, invalid or unexpected responses, etc.) + + \sa error() +*/ + +/*! + \fn void QNetworkReply::sslErrors(const QList<QSslError> &errors) + + This signal is emitted if the SSL/TLS session encountered errors + during the set up, including certificate verification errors. The + \a errors parameter contains the list of errors. + + To indicate that the errors are not fatal and that the connection + should proceed, the ignoreSslErrors() function should be called + from the slot connected to this signal. If it is not called, the + SSL session will be torn down before any data is exchanged + (including the URL). + + This signal can be used to display an error message to the user + indicating that security may be compromised and display the + SSL settings (see sslConfiguration() to obtain it). If the user + decides to proceed after analyzing the remote certificate, the + slot should call ignoreSslErrors(). + + \sa QSslSocket::sslErrors(), QNetworkAccessManager::sslErrors(), + sslConfiguration(), ignoreSslErrors() +*/ + +/*! + \fn void QNetworkReply::metaDataChanged() + + \omit FIXME: Update name? \endomit + + This signal is emitted whenever the metadata in this reply + changes. metadata is any information that is not the content + (data) itself, including the network headers. In the majority of + cases, the metadata will be known fully by the time the first + byte of data is received. However, it is possible to receive + updates of headers or other metadata during the processing of the + data. + + \sa header(), rawHeaderList(), rawHeader(), hasRawHeader() +*/ + +/*! + \fn void QNetworkReply::finished() + + This signal is emitted when the reply has finished + processing. After this signal is emitted, there will be no more + updates to the reply's data or metadata. + + Unless close() has been called, the reply will be still be opened + for reading, so the data can be retrieved by calls to read() or + readAll(). In particular, if no calls to read() were made as a + result of readyRead(), a call to readAll() will retrieve the full + contents in a QByteArray. + + This signal is emitted in tandem with + QNetworkAccessManager::finished() where that signal's reply + parameter is this object. + + \sa QNetworkAccessManager::finished() +*/ + +/*! + \fn void QNetworkReply::error(QNetworkReply::NetworkError code) + + This signal is emitted when the reply detects an error in + processing. The finished() signal will probably follow, indicating + that the connection is over. + + The \a code parameter contains the code of the error that was + detected. Call errorString() to obtain a textual representation of + the error condition. + + \sa error(), errorString() +*/ + +/*! + \fn void QNetworkReply::uploadProgress(qint64 bytesSent, qint64 bytesTotal) + + This signal is emitted to indicate the progress of the upload part + of this network request, if there's any. If there's no upload + associated with this request, this signal will not be emitted. + + The \a bytesSent + parameter indicates the number of bytes uploaded, while \a + bytesTotal indicates the total number of bytes to be uploaded. If + the number of bytes to be uploaded could not be determined, \a + bytesTotal will be -1. + + The upload is finished when \a bytesSent is equal to \a + bytesTotal. At that time, \a bytesTotal will not be -1. + + This signal is suitable to connecting to QProgressBar::setValue() + to update the QProgressBar that provides user feedback. + + \sa downloadProgress() +*/ + +/*! + \fn void QNetworkReply::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) + + This signal is emitted to indicate the progress of the download + part of this network request, if there's any. If there's no + download associated with this request, this signal will be emitted + once with 0 as the value of both \a bytesReceived and \a + bytesTotal. + + The \a bytesReceived parameter indicates the number of bytes + received, while \a bytesTotal indicates the total number of bytes + expected to be downloaded. If the number of bytes to be downloaded + is not known, \a bytesTotal will be -1. + + The download is finished when \a bytesReceived is equal to \a + bytesTotal. At that time, \a bytesTotal will not be -1. + + This signal is suitable to connecting to QProgressBar::setValue() + to update the QProgressBar that provides user feedback. + + Note that the values of both \a bytesReceived and \a bytesTotal + may be different from size(), the total number of bytes + obtained through read() or readAll(), or the value of the + header(ContentLengthHeader). The reason for that is that there may + be protocol overhead or the data may be compressed during the + download. + + \sa uploadProgress(), bytesAvailable() +*/ + +/*! + \fn void QNetworkReply::abort() + + Aborts the operation immediately and close down any network + connections still open. Uploads still in progress are also + aborted. + + \sa close() +*/ + +/*! + Creates a QNetworkReply object with parent \a parent. + + You cannot directly instantiate QNetworkReply objects. Use + QNetworkAccessManager functions to do that. +*/ +QNetworkReply::QNetworkReply(QObject *parent) + : QIODevice(*new QNetworkReplyPrivate, parent) +{ +} + +/*! + \internal +*/ +QNetworkReply::QNetworkReply(QNetworkReplyPrivate &dd, QObject *parent) + : QIODevice(dd, parent) +{ +} + +/*! + Disposes of this reply and frees any resources associated with + it. If any network connections are still open, they will be + closed. + + \sa abort(), close() +*/ +QNetworkReply::~QNetworkReply() +{ +} + +/*! + Closes this device for reading. Unread data is discarded, but the + network resources are not discarded until they are finished. In + particular, if any upload is in progress, it will continue until + it is done. + + The finished() signal is emitted when all operations are over and + the network resources are freed. + + \sa abort(), finished() +*/ +void QNetworkReply::close() +{ + QIODevice::close(); +} + +/*! + \internal +*/ +bool QNetworkReply::isSequential() const +{ + return true; +} + +/*! + Returns the size of the read buffer, in bytes. + + \sa setReadBufferSize() +*/ +qint64 QNetworkReply::readBufferSize() const +{ + return d_func()->readBufferMaxSize; +} + +/*! + Sets the size of the read buffer to be \a size bytes. The read + buffer is the buffer that holds data that is being downloaded off + the network, before it is read with QIODevice::read(). Setting the + buffer size to 0 will make the buffer unlimited in size. + + QNetworkReply will try to stop reading from the network once this + buffer is full (i.e., bytesAvailable() returns \a size or more), + thus causing the download to throttle down as well. If the buffer + is not limited in size, QNetworkReply will try to download as fast + as possible from the network. + + Unlike QAbstractSocket::setReadBufferSize(), QNetworkReply cannot + guarantee precision in the read buffer size. That is, + bytesAvailable() can return more than \a size. + + \sa readBufferSize() +*/ +void QNetworkReply::setReadBufferSize(qint64 size) +{ + Q_D(QNetworkReply); + d->readBufferMaxSize = size; +} + +/*! + Returns the QNetworkAccessManager that was used to create this + QNetworkReply object. Initially, it is also the parent object. +*/ +QNetworkAccessManager *QNetworkReply::manager() const +{ + return d_func()->manager; +} + +/*! + Returns the request that was posted for this reply. In special, + note that the URL for the request may be different than that of + the reply. + + \sa QNetworkRequest::url(), url(), setRequest() +*/ +QNetworkRequest QNetworkReply::request() const +{ + return d_func()->request; +} + +/*! + Returns the operation that was posted for this reply. + + \sa setOperation() +*/ +QNetworkAccessManager::Operation QNetworkReply::operation() const +{ + return d_func()->operation; +} + +/*! + Returns the error that was found during the processing of this + request. If no error was found, returns NoError. + + \sa setError() +*/ +QNetworkReply::NetworkError QNetworkReply::error() const +{ + return d_func()->errorCode; +} + +/*! + Returns the URL of the content downloaded or uploaded. Note that + the URL may be different from that of the original request. + + \sa request(), setUrl(), QNetworkRequest::url() +*/ +QUrl QNetworkReply::url() const +{ + return d_func()->url; +} + +/*! + Returns the value of the known header \a header, if that header + was sent by the remote server. If the header was not sent, returns + an invalid QVariant. + + \sa rawHeader(), setHeader(), QNetworkRequest::header() +*/ +QVariant QNetworkReply::header(QNetworkRequest::KnownHeaders header) const +{ + return d_func()->cookedHeaders.value(header); +} + +/*! + Returns true if the raw header of name \a headerName was sent by + the remote server + + \sa rawHeader() +*/ +bool QNetworkReply::hasRawHeader(const QByteArray &headerName) const +{ + Q_D(const QNetworkReply); + return d->findRawHeader(headerName) != d->rawHeaders.constEnd(); +} + +/*! + Returns the raw contents of the header \a headerName as sent by + the remote server. If there is no such header, returns an empty + byte array, which may be indistinguishable from an empty + header. Use hasRawHeader() to verify if the server sent such + header field. + + \sa setRawHeader(), hasRawHeader(), header() +*/ +QByteArray QNetworkReply::rawHeader(const QByteArray &headerName) const +{ + Q_D(const QNetworkReply); + QNetworkHeadersPrivate::RawHeadersList::ConstIterator it = + d->findRawHeader(headerName); + if (it != d->rawHeaders.constEnd()) + return it->second; + return QByteArray(); +} + +/*! + Returns a list of headers fields that were sent by the remote + server, in the order that they were sent. Duplicate headers are + merged together and take place of the latter duplicate. +*/ +QList<QByteArray> QNetworkReply::rawHeaderList() const +{ + return d_func()->rawHeadersKeys(); +} + +/*! + Returns the attribute associated with the code \a code. If the + attribute has not been set, it returns an invalid QVariant (type QVariant::Null). + + You can expect the default values listed in + QNetworkRequest::Attribute to be applied to the values returned by + this function. + + \sa setAttribute(), QNetworkRequest::Attribute +*/ +QVariant QNetworkReply::attribute(QNetworkRequest::Attribute code) const +{ + return d_func()->attributes.value(code); +} + +#ifndef QT_NO_OPENSSL +/*! + Returns the SSL configuration and state associated with this + reply, if SSL was used. It will contain the remote server's + certificate, its certificate chain leading to the Certificate + Authority as well as the encryption ciphers in use. + + The peer's certificate and its certificate chain will be known by + the time sslErrors() is emitted, if it's emitted. +*/ +QSslConfiguration QNetworkReply::sslConfiguration() const +{ + QSslConfiguration config; + + // determine if we support this extension + int id = metaObject()->indexOfMethod("sslConfigurationImplementation()"); + if (id != -1) { + void *arr[] = { &config, 0 }; + const_cast<QNetworkReply *>(this)->qt_metacall(QMetaObject::InvokeMetaMethod, id, arr); + } + return config; +} + +/*! + Sets the SSL configuration for the network connection associated + with this request, if possible, to be that of \a config. +*/ +void QNetworkReply::setSslConfiguration(const QSslConfiguration &config) +{ + if (config.isNull()) + return; + + int id = metaObject()->indexOfMethod("setSslConfigurationImplementation(QSslConfiguration)"); + if (id != -1) { + QSslConfiguration copy(config); + void *arr[] = { 0, © }; + qt_metacall(QMetaObject::InvokeMetaMethod, id, arr); + } +} +#endif + +/*! + If this function is called, SSL errors related to network + connection will be ignored, including certificate validation + errors. + + Note that calling this function without restraint may pose a + security risk for your application. Use it with care. + + This function can be called from the slot connected to the + sslErrors() signal, which indicates which errors were + found. + + \sa sslConfiguration(), sslErrors() +*/ +void QNetworkReply::ignoreSslErrors() +{ +} + +/*! + \internal +*/ +qint64 QNetworkReply::writeData(const char *, qint64) +{ + return -1; // you can't write +} + +/*! + Sets the associated operation for this object to be \a + operation. This value will be returned by operation(). + + Note: the operation should be set when this object is created and + not changed again. + + \sa operation(), setRequest() +*/ +void QNetworkReply::setOperation(QNetworkAccessManager::Operation operation) +{ + Q_D(QNetworkReply); + d->operation = operation; +} + +/*! + Sets the associated request for this object to be \a request. This + value will be returned by request(). + + Note: the request should be set when this object is created and + not changed again. + + \sa request(), setOperation() +*/ +void QNetworkReply::setRequest(const QNetworkRequest &request) +{ + Q_D(QNetworkReply); + d->request = request; +} + +/*! + Sets the error condition to be \a errorCode. The human-readable + message is set with \a errorString. + + Calling setError() does not emit the error(QNetworkReply::NetworkError) + signal. + + \sa error(), errorString() +*/ +void QNetworkReply::setError(NetworkError errorCode, const QString &errorString) +{ + Q_D(QNetworkReply); + d->errorCode = errorCode; + setErrorString(errorString); // in QIODevice +} + +/*! + Sets the URL being processed to be \a url. Normally, the URL + matches that of the request that was posted, but for a variety of + reasons it can be different (for example, a file path being made + absolute or canonical). + + \sa url(), request(), QNetworkRequest::url() +*/ +void QNetworkReply::setUrl(const QUrl &url) +{ + Q_D(QNetworkReply); + d->url = url; +} + +/*! + Sets the known header \a header to be of value \a value. The + corresponding raw form of the header will be set as well. + + \sa header(), setRawHeader(), QNetworkRequest::setHeader() +*/ +void QNetworkReply::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) +{ + Q_D(QNetworkReply); + d->setCookedHeader(header, value); +} + +/*! + Sets the raw header \a headerName to be of value \a value. If \a + headerName was previously set, it is overridden. Multiple HTTP + headers of the same name are functionally equivalent to one single + header with the values concatenated, separated by commas. + + If \a headerName matches a known header, the value \a value will + be parsed and the corresponding parsed form will also be set. + + \sa rawHeader(), header(), setHeader(), QNetworkRequest::setRawHeader() +*/ +void QNetworkReply::setRawHeader(const QByteArray &headerName, const QByteArray &value) +{ + Q_D(QNetworkReply); + d->setRawHeader(headerName, value); +} + +/*! + Sets the attribute \a code to have value \a value. If \a code was + previously set, it will be overridden. If \a value is an invalid + QVariant, the attribute will be unset. + + \sa attribute(), QNetworkRequest::setAttribute() +*/ +void QNetworkReply::setAttribute(QNetworkRequest::Attribute code, const QVariant &value) +{ + Q_D(QNetworkReply); + if (value.isValid()) + d->attributes.insert(code, value); + else + d->attributes.remove(code); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h new file mode 100644 index 0000000..6f763b3 --- /dev/null +++ b/src/network/access/qnetworkreply.h @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREPLY_H +#define QNETWORKREPLY_H + +#include <QtCore/QIODevice> +#include <QtCore/QString> +#include <QtCore/QVariant> + +#include <QtNetwork/QNetworkRequest> +#include <QtNetwork/QNetworkAccessManager> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QUrl; +class QVariant; +class QAuthenticator; +class QSslConfiguration; +class QSslError; + +class QNetworkReplyPrivate; +class Q_NETWORK_EXPORT QNetworkReply: public QIODevice +{ + Q_OBJECT + Q_ENUMS(NetworkError) +public: + enum NetworkError { + NoError = 0, + + // network layer errors [relating to the destination server] (1-99): + ConnectionRefusedError = 1, + RemoteHostClosedError, + HostNotFoundError, + TimeoutError, + OperationCanceledError, + SslHandshakeFailedError, + UnknownNetworkError = 99, + + // proxy errors (101-199): + ProxyConnectionRefusedError = 101, + ProxyConnectionClosedError, + ProxyNotFoundError, + ProxyTimeoutError, + ProxyAuthenticationRequiredError, + UnknownProxyError = 199, + + // content errors (201-299): + ContentAccessDenied = 201, + ContentOperationNotPermittedError, + ContentNotFoundError, + AuthenticationRequiredError, + UnknownContentError = 299, + + // protocol errors + ProtocolUnknownError = 301, + ProtocolInvalidOperationError, + ProtocolFailure = 399 + }; + + ~QNetworkReply(); + virtual void abort() = 0; + + // reimplemented from QIODevice + virtual void close(); + virtual bool isSequential() const; + + // like QAbstractSocket: + qint64 readBufferSize() const; + virtual void setReadBufferSize(qint64 size); + + QNetworkAccessManager *manager() const; + QNetworkAccessManager::Operation operation() const; + QNetworkRequest request() const; + NetworkError error() const; + QUrl url() const; + + // "cooked" headers + QVariant header(QNetworkRequest::KnownHeaders header) const; + + // raw headers: + bool hasRawHeader(const QByteArray &headerName) const; + QList<QByteArray> rawHeaderList() const; + QByteArray rawHeader(const QByteArray &headerName) const; + + // attributes + QVariant attribute(QNetworkRequest::Attribute code) const; + +#ifndef QT_NO_OPENSSL + QSslConfiguration sslConfiguration() const; + void setSslConfiguration(const QSslConfiguration &configuration); +#endif + +public Q_SLOTS: + virtual void ignoreSslErrors(); + +Q_SIGNALS: + void metaDataChanged(); + void finished(); + void error(QNetworkReply::NetworkError); +#ifndef QT_NO_OPENSSL + void sslErrors(const QList<QSslError> &errors); +#endif + + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + +protected: + QNetworkReply(QObject *parent = 0); + QNetworkReply(QNetworkReplyPrivate &dd, QObject *parent); + virtual qint64 writeData(const char *data, qint64 len); + + void setOperation(QNetworkAccessManager::Operation operation); + void setRequest(const QNetworkRequest &request); + void setError(NetworkError errorCode, const QString &errorString); + void setUrl(const QUrl &url); + void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); + void setRawHeader(const QByteArray &headerName, const QByteArray &value); + void setAttribute(QNetworkRequest::Attribute code, const QVariant &value); + +private: + Q_DECLARE_PRIVATE(QNetworkReply) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/access/qnetworkreply_p.h b/src/network/access/qnetworkreply_p.h new file mode 100644 index 0000000..f459e85 --- /dev/null +++ b/src/network/access/qnetworkreply_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREPLY_P_H +#define QNETWORKREPLY_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 "qnetworkrequest.h" +#include "qnetworkrequest_p.h" +#include "qnetworkreply.h" +#include "QtCore/qpointer.h" +#include "private/qiodevice_p.h" + +QT_BEGIN_NAMESPACE + +class QNetworkReplyPrivate: public QIODevicePrivate, public QNetworkHeadersPrivate +{ +public: + QNetworkReplyPrivate(); + QNetworkRequest request; + QUrl url; + QPointer<QNetworkAccessManager> manager; + qint64 readBufferMaxSize; + QNetworkAccessManager::Operation operation; + QNetworkReply::NetworkError errorCode; + + static inline void setManager(QNetworkReply *reply, QNetworkAccessManager *manager) + { reply->d_func()->manager = manager; } + + Q_DECLARE_PUBLIC(QNetworkReply) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp new file mode 100644 index 0000000..eaa572f --- /dev/null +++ b/src/network/access/qnetworkreplyimpl.cpp @@ -0,0 +1,598 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkreplyimpl_p.h" +#include "qnetworkaccessbackend_p.h" +#include "qnetworkcookie.h" +#include "qabstractnetworkcache.h" +#include "QtCore/qcoreapplication.h" +#include "QtCore/qdatetime.h" +#include "QtNetwork/qsslconfiguration.h" + +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE + +inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate() + : copyDevice(0), networkCache(0), + cacheEnabled(false), cacheSaveDevice(0), + bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), + state(Idle) +{ +} + +void QNetworkReplyImplPrivate::_q_startOperation() +{ + // This function is called exactly once + state = Working; + if (!backend) { + error(QNetworkReplyImpl::ProtocolUnknownError, + QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!; + finished(); + return; + } + + backend->open(); + if (state != Finished) { + if (operation == QNetworkAccessManager::GetOperation) + pendingNotifications.append(NotifyDownstreamReadyWrite); + if (outgoingData) { + _q_sourceReadyRead(); +#if 0 // ### FIXME + if (outgoingData->atEndOfStream() && writeBuffer.isEmpty()) + // empty upload + emit q->uploadProgress(0, 0); +#endif + } + + handleNotifications(); + } +} + +void QNetworkReplyImplPrivate::_q_sourceReadyRead() +{ + // read data from the outgoingData QIODevice into our internal buffer + enum { DesiredBufferSize = 32 * 1024 }; + + if (writeBuffer.size() >= DesiredBufferSize) + return; // don't grow the buffer too much + + // read as many bytes are available or up until we fill up the buffer + // but always read at least one byte + qint64 bytesToRead = qBound<qint64>(1, outgoingData->bytesAvailable(), + DesiredBufferSize - writeBuffer.size()); + char *ptr = writeBuffer.reserve(bytesToRead); + qint64 bytesActuallyRead = outgoingData->read(ptr, bytesToRead); + if (bytesActuallyRead == -1) { + // EOF + writeBuffer.chop(bytesToRead); + backendNotify(NotifyCloseUpstreamChannel); + return; + } + + if (bytesActuallyRead < bytesToRead) + writeBuffer.chop(bytesToRead - bytesActuallyRead); + + // if we did read anything, let the backend know and handle it + if (bytesActuallyRead) + backendNotify(NotifyUpstreamReadyRead); + + // check for EOF again + if (!outgoingData->isSequential() && outgoingData->atEnd()) + backendNotify(NotifyCloseUpstreamChannel); +} + +void QNetworkReplyImplPrivate::_q_sourceReadChannelFinished() +{ + _q_sourceReadyRead(); +} + +void QNetworkReplyImplPrivate::_q_copyReadyRead() +{ + Q_Q(QNetworkReplyImpl); + if (!copyDevice && !q->isOpen()) + return; + + qint64 bytesToRead = nextDownstreamBlockSize(); + if (bytesToRead == 0) + // we'll be called again, eventually + return; + + bytesToRead = qBound<qint64>(1, bytesToRead, copyDevice->bytesAvailable()); + char *ptr = readBuffer.reserve(bytesToRead); + qint64 bytesActuallyRead = copyDevice->read(ptr, bytesToRead); + if (bytesActuallyRead == -1) { + readBuffer.chop(bytesToRead); + backendNotify(NotifyCopyFinished); + return; + } + + if (bytesActuallyRead != bytesToRead) + readBuffer.chop(bytesToRead - bytesActuallyRead); + + if (!copyDevice->isSequential() && copyDevice->atEnd()) + backendNotify(NotifyCopyFinished); + + bytesDownloaded += bytesActuallyRead; + lastBytesDownloaded = bytesDownloaded; + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + emit q->downloadProgress(bytesDownloaded, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->readyRead(); +} + +void QNetworkReplyImplPrivate::_q_copyReadChannelFinished() +{ + _q_copyReadyRead(); +} + +void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, + QIODevice *data) +{ + Q_Q(QNetworkReplyImpl); + + outgoingData = data; + request = req; + url = request.url(); + operation = op; + + if (outgoingData) { + q->connect(outgoingData, SIGNAL(readyRead()), SLOT(_q_sourceReadyRead())); + q->connect(outgoingData, SIGNAL(readChannelFinished()), SLOT(_q_sourceReadChannelFinished())); + } + + q->QIODevice::open(QIODevice::ReadOnly); + QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection); +} + +void QNetworkReplyImplPrivate::setNetworkCache(QAbstractNetworkCache *nc) +{ + networkCache = nc; +} + +void QNetworkReplyImplPrivate::backendNotify(InternalNotifications notification) +{ + Q_Q(QNetworkReplyImpl); + if (!pendingNotifications.contains(notification)) + pendingNotifications.enqueue(notification); + + if (pendingNotifications.size() == 1) + QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated)); +} + +void QNetworkReplyImplPrivate::handleNotifications() +{ + NotificationQueue current = pendingNotifications; + pendingNotifications.clear(); + + if (state != Working) + return; + + while (!current.isEmpty()) { + InternalNotifications notification = current.dequeue(); + switch (notification) { + case NotifyDownstreamReadyWrite: + if (copyDevice) + _q_copyReadyRead(); + else + backend->downstreamReadyWrite(); + break; + + case NotifyUpstreamReadyRead: + backend->upstreamReadyRead(); + break; + + case NotifyCloseDownstreamChannel: + backend->closeDownstreamChannel(); + break; + + case NotifyCloseUpstreamChannel: + backend->closeUpstreamChannel(); + break; + + case NotifyCopyFinished: { + QIODevice *dev = copyDevice; + copyDevice = 0; + backend->copyFinished(dev); + break; + } + } + } +} + +void QNetworkReplyImplPrivate::createCache() +{ + // check if we can save and if we're allowed to + if (!networkCache || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool()) + return; + cacheEnabled = true; +} + +bool QNetworkReplyImplPrivate::isCachingEnabled() const +{ + return (cacheEnabled && networkCache != 0); +} + +void QNetworkReplyImplPrivate::setCachingEnabled(bool enable) +{ + if (!enable && !cacheEnabled) + return; // nothing to do + if (enable && cacheEnabled) + return; // nothing to do either! + + if (enable) { + if (bytesDownloaded) { + // refuse to enable in this case + qCritical("QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written"); + return; + } + + createCache(); + } else { + // someone told us to turn on, then back off? + // ok... but you should make up your mind + qDebug("QNetworkReplyImpl: setCachingEnabled(true) called after setCachingEnabled(false) -- " + "backend %s probably needs to be fixed", + backend->metaObject()->className()); + networkCache->remove(url); + cacheSaveDevice = 0; + cacheEnabled = false; + } +} + +void QNetworkReplyImplPrivate::completeCacheSave() +{ + if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) { + networkCache->remove(url); + } else if (cacheEnabled && cacheSaveDevice) { + networkCache->insert(cacheSaveDevice); + } + cacheSaveDevice = 0; + cacheEnabled = false; +} + +void QNetworkReplyImplPrivate::consume(qint64 count) +{ + Q_Q(QNetworkReplyImpl); + if (count <= 0) { + qWarning("QNetworkConnection: backend signalled that it consumed %ld bytes", long(count)); + return; + } + + if (outgoingData) + // schedule another read from the source + QMetaObject::invokeMethod(q_func(), "_q_sourceReadyRead", Qt::QueuedConnection); + + writeBuffer.skip(count); + if (bytesUploaded == -1) + bytesUploaded = count; + else + bytesUploaded += count; + + QVariant totalSize = request.header(QNetworkRequest::ContentLengthHeader); + emit q->uploadProgress(bytesUploaded, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); +} + +qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const +{ + enum { DesiredBufferSize = 32 * 1024 }; + if (readBufferMaxSize == 0) + return DesiredBufferSize; + + return qMax<qint64>(0, readBufferMaxSize - readBuffer.size()); +} + +void QNetworkReplyImplPrivate::feed(const QByteArray &data) +{ + Q_Q(QNetworkReplyImpl); + if (!q->isOpen()) + return; + + char *ptr = readBuffer.reserve(data.size()); + memcpy(ptr, data.constData(), data.size()); + + if (cacheEnabled && !cacheSaveDevice) { + // save the meta data + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + metaData = backend->fetchCacheMetaData(metaData); + cacheSaveDevice = networkCache->prepare(metaData); + if (!cacheSaveDevice || (cacheSaveDevice && !cacheSaveDevice->isOpen())) { + if (cacheSaveDevice && !cacheSaveDevice->isOpen()) + qCritical("QNetworkReplyImpl: network cache returned a device that is not open -- " + "class %s probably needs to be fixed", + networkCache->metaObject()->className()); + + networkCache->remove(url); + cacheSaveDevice = 0; + cacheEnabled = false; + } + } + + if (cacheSaveDevice) + cacheSaveDevice->write(data); + + bytesDownloaded += data.size(); + lastBytesDownloaded = bytesDownloaded; + + QPointer<QNetworkReplyImpl> qq = q; + + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + emit q->downloadProgress(bytesDownloaded, + totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong()); + emit q->readyRead(); + + // hopefully we haven't been deleted here + if (!qq.isNull()) { + // do we still have room in the buffer? + if (nextDownstreamBlockSize() > 0) + backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); + } +} + +void QNetworkReplyImplPrivate::feed(QIODevice *data) +{ + Q_Q(QNetworkReplyImpl); + Q_ASSERT(q->isOpen()); + + // read until EOF from data + if (copyDevice) { + qCritical("QNetworkReplyImpl: copy from QIODevice already in progress -- " + "backend probly needs to be fixed"); + return; + } + + copyDevice = data; + q->connect(copyDevice, SIGNAL(readyRead()), SLOT(_q_copyReadyRead())); + q->connect(copyDevice, SIGNAL(readChannelFinished()), SLOT(_q_copyReadChannelFinished())); + + // start the copy: + _q_copyReadyRead(); +} + +void QNetworkReplyImplPrivate::finished() +{ + Q_Q(QNetworkReplyImpl); + Q_ASSERT_X(state != Finished, "QNetworkReplyImpl", + "Backend called finished/finishedWithError more than once"); + + state = Finished; + pendingNotifications.clear(); + + QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); + if (bytesDownloaded != lastBytesDownloaded || totalSize.isNull()) + emit q->downloadProgress(bytesDownloaded, bytesDownloaded); + if (bytesUploaded == -1 && outgoingData) + emit q->uploadProgress(0, 0); + + completeCacheSave(); + + // note: might not be a good idea, since users could decide to delete us + // which would delete the backend too... + // maybe we should protect the backend + emit q->readChannelFinished(); + emit q->finished(); +} + +void QNetworkReplyImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage) +{ + Q_Q(QNetworkReplyImpl); + + errorCode = code; + q->setErrorString(errorMessage); + + // note: might not be a good idea, since users could decide to delete us + // which would delete the backend too... + // maybe we should protect the backend + emit q->error(code); +} + +void QNetworkReplyImplPrivate::metaDataChanged() +{ + Q_Q(QNetworkReplyImpl); + // do we have cookies? + if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()) { + QList<QNetworkCookie> cookies = + qvariant_cast<QList<QNetworkCookie> >(cookedHeaders.value(QNetworkRequest::SetCookieHeader)); + QNetworkCookieJar *jar = manager->cookieJar(); + if (jar) + jar->setCookiesFromUrl(cookies, url); + } + emit q->metaDataChanged(); +} + +void QNetworkReplyImplPrivate::redirectionRequested(const QUrl &target) +{ + attributes.insert(QNetworkRequest::RedirectionTargetAttribute, target); +} + +void QNetworkReplyImplPrivate::sslErrors(const QList<QSslError> &errors) +{ +#ifndef QT_NO_OPENSSL + Q_Q(QNetworkReplyImpl); + emit q->sslErrors(errors); +#else + Q_UNUSED(errors); +#endif +} + +QNetworkReplyImpl::QNetworkReplyImpl(QObject *parent) + : QNetworkReply(*new QNetworkReplyImplPrivate, parent) +{ +} + +QNetworkReplyImpl::~QNetworkReplyImpl() +{ + Q_D(QNetworkReplyImpl); + if (d->isCachingEnabled()) + d->networkCache->remove(url()); +} + +void QNetworkReplyImpl::abort() +{ + Q_D(QNetworkReplyImpl); + if (d->state == QNetworkReplyImplPrivate::Aborted) + return; + + // stop both upload and download + if (d->backend) { + d->backend->deleteLater(); + d->backend = 0; + } + if (d->outgoingData) + disconnect(d->outgoingData, 0, this, 0); + if (d->copyDevice) + disconnect(d->copyDevice, 0, this, 0); + + QNetworkReply::close(); + + if (d->state != QNetworkReplyImplPrivate::Finished) { + // emit signals + d->error(OperationCanceledError, tr("Operation canceled")); + d->finished(); + } + d->state = QNetworkReplyImplPrivate::Aborted; +} + +void QNetworkReplyImpl::close() +{ + Q_D(QNetworkReplyImpl); + if (d->state == QNetworkReplyImplPrivate::Aborted || + d->state == QNetworkReplyImplPrivate::Finished) + return; + + // stop the download + if (d->backend) + d->backend->closeDownstreamChannel(); + if (d->copyDevice) + disconnect(d->copyDevice, 0, this, 0); + + QNetworkReply::close(); + + // emit signals + d->error(OperationCanceledError, tr("Operation canceled")); + d->finished(); +} + +/*! + Returns the number of bytes available for reading with + QIODevice::read(). The number of bytes available may grow until + the finished() signal is emitted. +*/ +qint64 QNetworkReplyImpl::bytesAvailable() const +{ + return QNetworkReply::bytesAvailable() + d_func()->readBuffer.size(); +} + +void QNetworkReplyImpl::setReadBufferSize(qint64 size) +{ + Q_D(QNetworkReplyImpl); + if (size > d->readBufferMaxSize && + size == d->readBuffer.size()) + d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); + + QNetworkReply::setReadBufferSize(size); +} + +#ifndef QT_NO_OPENSSL +QSslConfiguration QNetworkReplyImpl::sslConfigurationImplementation() const +{ + Q_D(const QNetworkReplyImpl); + QSslConfiguration config; + if (d->backend) + d->backend->fetchSslConfiguration(config); + return config; +} + +void QNetworkReplyImpl::setSslConfigurationImplementation(const QSslConfiguration &config) +{ + Q_D(QNetworkReplyImpl); + if (d->backend && !config.isNull()) + d->backend->setSslConfiguration(config); +} + +void QNetworkReplyImpl::ignoreSslErrors() +{ + Q_D(QNetworkReplyImpl); + if (d->backend) + d->backend->ignoreSslErrors(); +} + +#endif // QT_NO_OPENSSL + +/*! + \internal +*/ +qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen) +{ + Q_D(QNetworkReplyImpl); + if (d->readBuffer.isEmpty()) + return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0; + + d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite); + if (maxlen == 1) { + // optimization for getChar() + *data = d->readBuffer.getChar(); + return 1; + } + + maxlen = qMin<qint64>(maxlen, d->readBuffer.size()); + return d->readBuffer.read(data, maxlen); +} + +/*! + \internal Reimplemented for internal purposes +*/ +bool QNetworkReplyImpl::event(QEvent *e) +{ + if (e->type() == QEvent::NetworkReplyUpdated) { + d_func()->handleNotifications(); + return true; + } + + return QObject::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qnetworkreplyimpl_p.cpp" + diff --git a/src/network/access/qnetworkreplyimpl_p.h b/src/network/access/qnetworkreplyimpl_p.h new file mode 100644 index 0000000..ad06f78 --- /dev/null +++ b/src/network/access/qnetworkreplyimpl_p.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREPLYIMPL_P_H +#define QNETWORKREPLYIMPL_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 "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include "qnetworkaccessmanager.h" +#include "qnetworkproxy.h" +#include "QtCore/qmap.h" +#include "QtCore/qqueue.h" +#include "private/qringbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class QAbstractNetworkCache; +class QNetworkAccessBackend; + +class QNetworkReplyImplPrivate; +class QNetworkReplyImpl: public QNetworkReply +{ + Q_OBJECT +public: + QNetworkReplyImpl(QObject *parent = 0); + ~QNetworkReplyImpl(); + virtual void abort(); + + // reimplemented from QNetworkReply + virtual void close(); + virtual qint64 bytesAvailable() const; + virtual void setReadBufferSize(qint64 size); + + virtual qint64 readData(char *data, qint64 maxlen); + virtual bool event(QEvent *); + +#ifndef QT_NO_OPENSSL + Q_INVOKABLE QSslConfiguration sslConfigurationImplementation() const; + Q_INVOKABLE void setSslConfigurationImplementation(const QSslConfiguration &configuration); + virtual void ignoreSslErrors(); +#endif + + Q_DECLARE_PRIVATE(QNetworkReplyImpl) + Q_PRIVATE_SLOT(d_func(), void _q_startOperation()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceReadyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceReadChannelFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_copyReadyRead()) + Q_PRIVATE_SLOT(d_func(), void _q_copyReadChannelFinished()) +}; + +class QNetworkReplyImplPrivate: public QNetworkReplyPrivate +{ +public: + enum InternalNotifications { + NotifyDownstreamReadyWrite, + NotifyUpstreamReadyRead, + NotifyCloseDownstreamChannel, + NotifyCloseUpstreamChannel, + NotifyCopyFinished + }; + + enum State { + Idle, + Opening, + Working, + Finished, + Aborted + }; + + typedef QQueue<InternalNotifications> NotificationQueue; + + QNetworkReplyImplPrivate(); + + void _q_startOperation(); + void _q_sourceReadyRead(); + void _q_sourceReadChannelFinished(); + void _q_copyReadyRead(); + void _q_copyReadChannelFinished(); + + void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QIODevice *outgoingData); + void setNetworkCache(QAbstractNetworkCache *networkCache); + void backendNotify(InternalNotifications notification); + void handleNotifications(); + void createCache(); + void completeCacheSave(); + + // callbacks from the backend (through the manager): + void setCachingEnabled(bool enable); + bool isCachingEnabled() const; + void consume(qint64 count); + qint64 nextDownstreamBlockSize() const; + void feed(const QByteArray &data); + void feed(QIODevice *data); + void finished(); + void error(QNetworkReply::NetworkError code, const QString &errorString); + void metaDataChanged(); + void redirectionRequested(const QUrl &target); + void sslErrors(const QList<QSslError> &errors); + + QNetworkAccessBackend *backend; + QIODevice *outgoingData; + QIODevice *copyDevice; + QAbstractNetworkCache *networkCache; + + bool cacheEnabled; + QIODevice *cacheSaveDevice; + + NotificationQueue pendingNotifications; + QUrl urlForLastAuthentication; +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy lastProxyAuthentication; + QList<QNetworkProxy> proxyList; +#endif + + QRingBuffer readBuffer; + QRingBuffer writeBuffer; + qint64 bytesDownloaded; + qint64 lastBytesDownloaded; + qint64 bytesUploaded; + + QString httpReasonPhrase; + int httpStatusCode; + + State state; + + Q_DECLARE_PUBLIC(QNetworkReplyImpl) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp new file mode 100644 index 0000000..56b793d --- /dev/null +++ b/src/network/access/qnetworkrequest.cpp @@ -0,0 +1,803 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qnetworkrequest.h" +#include "qnetworkcookie.h" +#include "qnetworkrequest_p.h" +#include "qsslconfiguration.h" +#include "QtCore/qshareddata.h" +#include "QtCore/qlocale.h" +#include "QtCore/qdatetime.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QNetworkRequest + \brief The QNetworkRequest class holds one request to be sent with the Network Access API. + \since 4.4 + + \ingroup io + \inmodule QtNetwork + + QNetworkRequest is part of the Network Access API and is the class + holding the information necessary to send a request over the + network. It contains a URL and some ancillary information that can + be used to modify the request. + + \sa QNetworkReply, QNetworkAccessManager +*/ + +/*! + \enum QNetworkRequest::KnownHeaders + + List of known header types that QNetworkRequest parses. Each known + header is also represented in raw form with its full HTTP name. + + \value ContentTypeHeader corresponds to the HTTP Content-Type + header and contains a string containing the media (MIME) type and + any auxiliary data (for instance, charset) + + \value ContentLengthHeader corresponds to the HTTP Content-Length + header and contains the length in bytes of the data transmitted. + + \value LocationHeader corresponds to the HTTP Location + header and contains a URL representing the actual location of the + data, including the destination URL in case of redirections. + + \value LastModifiedHeader corresponds to the HTTP Last-Modified + header and contains a QDateTime representing the last modification + date of the contents + + \value CookieHeader corresponds to the HTTP Cookie header + and contains a QList<QNetworkCookie> representing the cookies to + be sent back to the server + + \value SetCookieHeader corresponds to the HTTP Set-Cookie + header and contains a QList<QNetworkCookie> representing the + cookies sent by the server to be stored locally + + \sa header(), setHeader(), rawHeader(), setRawHeader() +*/ + +/*! + \enum QNetworkRequest::Attribute + + Attribute codes for the QNetworkRequest and QNetworkReply. + + Attributes are extra meta-data that are used to control the + behavior of the request and to pass further information from the + reply back to the application. Attributes are also extensible, + allowing custom implementations to pass custom values. + + The following table explains what the default attribute codes are, + the QVariant types associated, the default value if said attribute + is missing and whether it's used in requests or replies. + + \value HttpStatusCodeAttribute + Replies only, type: QVariant::Int (no default) + Indicates the HTTP status code received from the HTTP server + (like 200, 304, 404, 401, etc.). If the connection was not + HTTP-based, this attribute will not be present. + + \value HttpReasonPhraseAttribute + Replies only, type: QVariant::ByteArray (no default) + Indicates the HTTP reason phrase as received from the HTTP + server (like "Ok", "Found", "Not Found", "Access Denied", + etc.) This is the human-readable representation of the status + code (see above). If the connection was not HTTP-based, this + attribute will not be present. + + \value RedirectionTargetAttribute + Replies only, type: QVariant::Url (no default) + If present, it indicates that the server is redirecting the + request to a different URL. The Network Access API does not by + default follow redirections: it's up to the application to + determine if the requested redirection should be allowed, + according to its security policies. + + \value ConnectionEncryptedAttribute + Replies only, type: QVariant::Bool (default: false) + Indicates whether the data was obtained through an encrypted + (secure) connection. + + \value CacheLoadControlAttribute + Requests only, type: QVariant::Int (default: QNetworkRequest::PreferNetwork) + Controls how the cache should be accessed. The possible values + are those of QNetworkRequest::CacheLoadControl. Note that the + default QNetworkAccessManager implementation does not support + caching. However, this attribute may be used by certain + backends to modify their requests (for example, for caching proxies). + + \value CacheSaveControlAttribute + Requests only, type: QVariant::Bool (default: true) + Controls if the data obtained should be saved to cache for + future uses. If the value is false, the data obtained will not + be automatically cached. If true, data may be cached, provided + it is cacheable (what is cacheable depends on the protocol + being used). Note that the default QNetworkAccessManager + implementation does not support caching, so it will ignore + this attribute. + + \value SourceIsFromCacheAttribute + Replies only, type: QVariant::Bool (default: false) + Indicates whether the data was obtained from cache + or not. + + \value User + Special type. Additional information can be passed in + QVariants with types ranging from User to UserMax. The default + implementation of Network Access will ignore any request + attributes in this range and it will not produce any + attributes in this range in replies. The range is reserved for + extensions of QNetworkAccessManager. + + \value UserMax + Special type. See User. +*/ + +/*! + \enum QNetworkRequest::CacheLoadControl + + Controls the caching mechanism of QNetworkAccessManager. + + \value AlwaysNetwork always load from network and do not + check if the cache has a valid entry (similar to the + "Reload" feature in browsers) + + \value PreferNetwork default value; load from the network + if the cached entry is older than the network entry + + \value PreferCache load from cache if available, + otherwise load from network. Note that this can return possibly + stale (but not expired) items from cache. + + \value AlwaysCache only load from cache, indicating error + if the item was not cached (i.e., off-line mode) +*/ + +class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate +{ +public: + inline QNetworkRequestPrivate() +#ifndef QT_NO_OPENSSL + : sslConfiguration(0) +#endif + { qRegisterMetaType<QNetworkRequest>(); } + ~QNetworkRequestPrivate() + { +#ifndef QT_NO_OPENSSL + delete sslConfiguration; +#endif + } + + + QNetworkRequestPrivate(const QNetworkRequestPrivate &other) + : QSharedData(other), QNetworkHeadersPrivate(other) + { + url = other.url; + +#ifndef QT_NO_OPENSSL + if (other.sslConfiguration) + sslConfiguration = new QSslConfiguration(*other.sslConfiguration); + else + sslConfiguration = 0; +#endif + } + + inline bool operator==(const QNetworkRequestPrivate &other) const + { + return url == other.url && + rawHeaders == other.rawHeaders && + attributes == other.attributes; + // don't compare cookedHeaders + } + + QUrl url; +#ifndef QT_NO_OPENSSL + mutable QSslConfiguration *sslConfiguration; +#endif +}; + +/*! + Constructs a QNetworkRequest object with \a url as the URL to be + requested. + + \sa url(), setUrl() +*/ +QNetworkRequest::QNetworkRequest(const QUrl &url) + : d(new QNetworkRequestPrivate) +{ + d->url = url; +} + +/*! + Creates a copy of \a other. +*/ +QNetworkRequest::QNetworkRequest(const QNetworkRequest &other) + : d(other.d) +{ +} + +/*! + Disposes of the QNetworkRequest object. +*/ +QNetworkRequest::~QNetworkRequest() +{ + // QSharedDataPointer auto deletes + d = 0; +} + +/*! + Returns true if this object is the same as \a other (i.e., if they + have the same URL, same headers and same meta-data settings). + + \sa operator!=() +*/ +bool QNetworkRequest::operator==(const QNetworkRequest &other) const +{ + return d == other.d || *d == *other.d; +} + +/*! + \fn bool QNetworkRequest::operator!=(const QNetworkRequest &other) const + + Returns false if this object is not the same as \a other. + + \sa operator==() +*/ + +/*! + Creates a copy of \a other +*/ +QNetworkRequest &QNetworkRequest::operator=(const QNetworkRequest &other) +{ + d = other.d; + return *this; +} + +/*! + Returns the URL this network request is referring to. + + \sa setUrl() +*/ +QUrl QNetworkRequest::url() const +{ + return d->url; +} + +/*! + Sets the URL this network request is referring to to be \a url. + + \sa url() +*/ +void QNetworkRequest::setUrl(const QUrl &url) +{ + d->url = url; +} + +/*! + Returns the value of the known network header \a header if it is + present in this request. If it is not present, returns QVariant() + (i.e., an invalid variant). + + \sa KnownHeaders, rawHeader(), setHeader() +*/ +QVariant QNetworkRequest::header(KnownHeaders header) const +{ + return d->cookedHeaders.value(header); +} + +/*! + Sets the value of the known header \a header to be \a value, + overriding any previously set headers. This operation also sets + the equivalent raw HTTP header. + + \sa KnownHeaders, setRawHeader(), header() +*/ +void QNetworkRequest::setHeader(KnownHeaders header, const QVariant &value) +{ + d->setCookedHeader(header, value); +} + +/*! + Returns true if the raw header \a headerName is present in this + network request. + + \sa rawHeader(), setRawHeader() +*/ +bool QNetworkRequest::hasRawHeader(const QByteArray &headerName) const +{ + return d->findRawHeader(headerName) != d->rawHeaders.constEnd(); +} + +/*! + Returns the raw form of header \a headerName. If no such header is + present, an empty QByteArray is returned, which may be + indistinguishable from a header that is present but has no content + (use hasRawHeader() to find out if the header exists or not). + + Raw headers can be set with setRawHeader() or with setHeader(). + + \sa header(), setRawHeader() +*/ +QByteArray QNetworkRequest::rawHeader(const QByteArray &headerName) const +{ + QNetworkHeadersPrivate::RawHeadersList::ConstIterator it = + d->findRawHeader(headerName); + if (it != d->rawHeaders.constEnd()) + return it->second; + return QByteArray(); +} + +/*! + Returns a list of all raw headers that are set in this network + request. The list is in the order that the headers were set. + + \sa hasRawHeader(), rawHeader() +*/ +QList<QByteArray> QNetworkRequest::rawHeaderList() const +{ + return d->rawHeadersKeys(); +} + +/*! + 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. + + For example: + \snippet doc/src/snippets/code/src_network_access_qnetworkrequest.cpp 0 + + will also set the known header LastModifiedHeader to be the + QDateTime object of the parsed date. + + 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 KnownHeaders, setHeader(), hasRawHeader(), rawHeader() +*/ +void QNetworkRequest::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue) +{ + d->setRawHeader(headerName, headerValue); +} + +/*! + Returns the attribute associated with the code \a code. If the + attribute has not been set, it returns \a defaultValue. + + Note: this function does not apply the defaults listed in + QNetworkRequest::Attribute. + + \sa setAttribute(), QNetworkRequest::Attribute +*/ +QVariant QNetworkRequest::attribute(Attribute code, const QVariant &defaultValue) const +{ + return d->attributes.value(code, defaultValue); +} + +/*! + Sets the attribute associated with code \a code to be value \a + value. If the attribute is already set, the previous value is + discarded. In special, if \a value is an invalid QVariant, the + attribute is unset. + + \sa attribute(), QNetworkRequest::Attribute +*/ +void QNetworkRequest::setAttribute(Attribute code, const QVariant &value) +{ + if (value.isValid()) + d->attributes.insert(code, value); + else + d->attributes.remove(code); +} + +#ifndef QT_NO_OPENSSL +/*! + Returns this network request's SSL configuration. By default, no + SSL settings are specified. + + \sa setSslConfiguration() +*/ +QSslConfiguration QNetworkRequest::sslConfiguration() const +{ + if (!d->sslConfiguration) + d->sslConfiguration = new QSslConfiguration; + return *d->sslConfiguration; +} + +/*! + Sets this network request's SSL configuration to be \a config. The + settings that apply are the private key, the local certificate, + the SSL protocol (SSLv2, SSLv3, TLSv1 where applicable) and the + ciphers that the SSL backend is allowed to use. + + By default, no SSL configuration is set, which allows the backends + to choose freely what configuration is best for them. + + \sa sslConfiguration(), QSslConfiguration::defaultConfiguration() +*/ +void QNetworkRequest::setSslConfiguration(const QSslConfiguration &config) +{ + if (!d->sslConfiguration) + d->sslConfiguration = new QSslConfiguration(config); + else + *d->sslConfiguration = config; +} +#endif + +static QByteArray headerName(QNetworkRequest::KnownHeaders header) +{ + switch (header) { + case QNetworkRequest::ContentTypeHeader: + return "Content-Type"; + + case QNetworkRequest::ContentLengthHeader: + return "Content-Length"; + + case QNetworkRequest::LocationHeader: + return "Location"; + + case QNetworkRequest::LastModifiedHeader: + return "Last-Modified"; + + case QNetworkRequest::CookieHeader: + return "Cookie"; + + case QNetworkRequest::SetCookieHeader: + return "Set-Cookie"; + + // no default: + // if new values are added, this will generate a compiler warning + } + + return QByteArray(); +} + +static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVariant &value) +{ + switch (header) { + case QNetworkRequest::ContentTypeHeader: + case QNetworkRequest::ContentLengthHeader: + return value.toByteArray(); + + case QNetworkRequest::LocationHeader: + switch (value.type()) { + case QVariant::Url: + return value.toUrl().toEncoded(); + + default: + return value.toByteArray(); + } + + case QNetworkRequest::LastModifiedHeader: + switch (value.type()) { + case QVariant::Date: + case QVariant::DateTime: + // generate RFC 1123/822 dates: + return QNetworkHeadersPrivate::toHttpDate(value.toDateTime()); + + default: + return value.toByteArray(); + } + + case QNetworkRequest::CookieHeader: { + QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(value); + if (cookies.isEmpty() && value.userType() == qMetaTypeId<QNetworkCookie>()) + cookies << qvariant_cast<QNetworkCookie>(value); + + QByteArray result; + bool first = true; + foreach (const QNetworkCookie &cookie, cookies) { + if (!first) + result += "; "; + first = false; + result += cookie.toRawForm(QNetworkCookie::NameAndValueOnly); + } + return result; + } + + case QNetworkRequest::SetCookieHeader: { + QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(value); + if (cookies.isEmpty() && value.userType() == qMetaTypeId<QNetworkCookie>()) + cookies << qvariant_cast<QNetworkCookie>(value); + + QByteArray result; + bool first = true; + foreach (const QNetworkCookie &cookie, cookies) { + if (!first) + result += ", "; + first = false; + result += cookie.toRawForm(QNetworkCookie::Full); + } + return result; + } + } + + return QByteArray(); +} + +static QNetworkRequest::KnownHeaders parseHeaderName(const QByteArray &headerName) +{ + // headerName is not empty here + + QByteArray lower = headerName.toLower(); + switch (lower.at(0)) { + case 'c': + if (lower == "content-type") + return QNetworkRequest::ContentTypeHeader; + else if (lower == "content-length") + return QNetworkRequest::ContentLengthHeader; + else if (lower == "cookie") + return QNetworkRequest::CookieHeader; + break; + + case 'l': + if (lower == "location") + return QNetworkRequest::LocationHeader; + else if (lower == "last-modified") + return QNetworkRequest::LastModifiedHeader; + break; + + case 's': + if (lower == "set-cookie") + return QNetworkRequest::SetCookieHeader; + break; + } + + return QNetworkRequest::KnownHeaders(-1); // nothing found +} + +static QVariant parseHttpDate(const QByteArray &raw) +{ + QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(raw); + if (dt.isValid()) + return dt; + return QVariant(); // transform an invalid QDateTime into a null QVariant +} + +static QVariant parseCookieHeader(const QByteArray &raw) +{ + QList<QNetworkCookie> result; + QList<QByteArray> cookieList = raw.split(';'); + foreach (QByteArray cookie, cookieList) { + QList<QNetworkCookie> parsed = QNetworkCookie::parseCookies(cookie.trimmed()); + if (parsed.count() != 1) + return QVariant(); // invalid Cookie: header + + result += parsed; + } + + return qVariantFromValue(result); +} + +static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QByteArray &value) +{ + // header is always a valid value + switch (header) { + case QNetworkRequest::ContentTypeHeader: + // copy exactly, convert to QString + return QString::fromLatin1(value); + + case QNetworkRequest::ContentLengthHeader: { + bool ok; + qint64 result = value.trimmed().toLongLong(&ok); + if (ok) + return result; + return QVariant(); + } + + case QNetworkRequest::LocationHeader: { + QUrl result = QUrl::fromEncoded(value, QUrl::StrictMode); + if (result.isValid() && !result.scheme().isEmpty()) + return result; + return QVariant(); + } + + case QNetworkRequest::LastModifiedHeader: + return parseHttpDate(value); + + case QNetworkRequest::CookieHeader: + return parseCookieHeader(value); + + case QNetworkRequest::SetCookieHeader: + return qVariantFromValue(QNetworkCookie::parseCookies(value)); + + default: + Q_ASSERT(0); + } + return QVariant(); +} + +QNetworkHeadersPrivate::RawHeadersList::ConstIterator +QNetworkHeadersPrivate::findRawHeader(const QByteArray &key) const +{ + QByteArray lowerKey = key.toLower(); + RawHeadersList::ConstIterator it = rawHeaders.constBegin(); + RawHeadersList::ConstIterator end = rawHeaders.constEnd(); + for ( ; it != end; ++it) + if (it->first.toLower() == lowerKey) + return it; + + return end; // not found +} + +QList<QByteArray> QNetworkHeadersPrivate::rawHeadersKeys() const +{ + QList<QByteArray> result; + RawHeadersList::ConstIterator it = rawHeaders.constBegin(), + end = rawHeaders.constEnd(); + for ( ; it != end; ++it) + result << it->first; + + return result; +} + +void QNetworkHeadersPrivate::setRawHeader(const QByteArray &key, const QByteArray &value) +{ + if (key.isEmpty()) + // refuse to accept an empty raw header + return; + + setRawHeaderInternal(key, value); + parseAndSetHeader(key, value); +} + +/*! + \internal + Sets the internal raw headers list to match \a list. The cooked headers + will also be updated. + + If \a list contains duplicates, they will be stored, but only the first one + is usually accessed. +*/ +void QNetworkHeadersPrivate::setAllRawHeaders(const RawHeadersList &list) +{ + cookedHeaders.clear(); + rawHeaders = list; + + RawHeadersList::ConstIterator it = rawHeaders.constBegin(); + RawHeadersList::ConstIterator end = rawHeaders.constEnd(); + for ( ; it != end; ++it) + parseAndSetHeader(it->first, it->second); +} + +void QNetworkHeadersPrivate::setCookedHeader(QNetworkRequest::KnownHeaders header, + const QVariant &value) +{ + QByteArray name = headerName(header); + if (name.isEmpty()) { + // headerName verifies that \a header is a known value + qWarning("QNetworkRequest::setHeader: invalid header value KnownHeader(%d) received", header); + return; + } + + if (value.isNull()) { + setRawHeaderInternal(name, QByteArray()); + cookedHeaders.remove(header); + } else { + QByteArray rawValue = headerValue(header, value); + if (rawValue.isEmpty()) { + qWarning("QNetworkRequest::setHeader: QVariant of type %s cannot be used with header %s", + value.typeName(), name.constData()); + return; + } + + setRawHeaderInternal(name, rawValue); + cookedHeaders.insert(header, value); + } +} + +void QNetworkHeadersPrivate::setRawHeaderInternal(const QByteArray &key, const QByteArray &value) +{ + QByteArray lowerKey = key.toLower(); + RawHeadersList::Iterator it = rawHeaders.begin(); + while (it != rawHeaders.end()) { + if (it->first.toLower() == lowerKey) + it = rawHeaders.erase(it); + else + ++it; + } + + if (value.isNull()) + return; // only wanted to erase key + + RawHeaderPair pair; + pair.first = key; + pair.second = value; + rawHeaders.append(pair); +} + +void QNetworkHeadersPrivate::parseAndSetHeader(const QByteArray &key, const QByteArray &value) +{ + // is it a known header? + QNetworkRequest::KnownHeaders parsedKey = parseHeaderName(key); + if (parsedKey != QNetworkRequest::KnownHeaders(-1)) { + if (value.isNull()) + cookedHeaders.remove(parsedKey); + else + cookedHeaders.insert(parsedKey, parseHeaderValue(parsedKey, value)); + } +} + +QDateTime QNetworkHeadersPrivate::fromHttpDate(const QByteArray &value) +{ + // HTTP dates have three possible formats: + // RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT" + // RFC 850 - dddd, dd-MMM-yy hh:mm:ss "GMT" + // ANSI C's asctime - ddd MMM d hh:mm:ss yyyy + // We only handle them exactly. If they deviate, we bail out. + + int pos = value.indexOf(','); + QDateTime dt; +#ifndef QT_NO_DATESTRING + if (pos == -1) { + // no comma -> asctime(3) format + dt = QDateTime::fromString(QString::fromLatin1(value), Qt::TextDate); + } else { + // eat the weekday, the comma and the space following it + QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2); + + QLocale c = QLocale::c(); + if (pos == 3) + // must be RFC 1123 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd MMM yyyy hh:mm:ss 'GMT")); + else + // must be RFC 850 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd-MMM-yy hh:mm:ss 'GMT'")); + } +#endif // QT_NO_DATESTRING + + if (dt.isValid()) + dt.setTimeSpec(Qt::UTC); + return dt; +} + +QByteArray QNetworkHeadersPrivate::toHttpDate(const QDateTime &dt) +{ + return QLocale::c().toString(dt, QLatin1String("ddd, dd MMM yyyy hh:mm:ss 'GMT'")) + .toLatin1(); +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h new file mode 100644 index 0000000..6f34bce --- /dev/null +++ b/src/network/access/qnetworkrequest.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREQUEST_H +#define QNETWORKREQUEST_H + +#include <QtCore/QSharedDataPointer> +#include <QtCore/QString> +#include <QtCore/QUrl> +#include <QtCore/QVariant> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QSslConfiguration; + +class QNetworkRequestPrivate; +class Q_NETWORK_EXPORT QNetworkRequest +{ +public: + enum KnownHeaders { + ContentTypeHeader, + ContentLengthHeader, + LocationHeader, + LastModifiedHeader, + CookieHeader, + SetCookieHeader + }; + enum Attribute { + HttpStatusCodeAttribute, + HttpReasonPhraseAttribute, + RedirectionTargetAttribute, + ConnectionEncryptedAttribute, + CacheLoadControlAttribute, + CacheSaveControlAttribute, + SourceIsFromCacheAttribute, + + User = 1000, + UserMax = 32767 + }; + enum CacheLoadControl { + AlwaysNetwork, + PreferNetwork, + PreferCache, + AlwaysCache + }; + + explicit QNetworkRequest(const QUrl &url = QUrl()); + QNetworkRequest(const QNetworkRequest &other); + ~QNetworkRequest(); + QNetworkRequest &operator=(const QNetworkRequest &other); + + bool operator==(const QNetworkRequest &other) const; + inline bool operator!=(const QNetworkRequest &other) const + { return !operator==(other); } + + QUrl url() const; + void setUrl(const QUrl &url); + + // "cooked" headers + QVariant header(KnownHeaders header) const; + void setHeader(KnownHeaders header, const QVariant &value); + + // raw headers: + bool hasRawHeader(const QByteArray &headerName) const; + QList<QByteArray> rawHeaderList() const; + QByteArray rawHeader(const QByteArray &headerName) const; + void setRawHeader(const QByteArray &headerName, const QByteArray &value); + + // attributes + QVariant attribute(Attribute code, const QVariant &defaultValue = QVariant()) const; + void setAttribute(Attribute code, const QVariant &value); + +#ifndef QT_NO_OPENSSL + QSslConfiguration sslConfiguration() const; + void setSslConfiguration(const QSslConfiguration &configuration); +#endif + +private: + QSharedDataPointer<QNetworkRequestPrivate> d; + friend class QNetworkRequestPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QNetworkRequest) + +QT_END_HEADER + +#endif diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h new file mode 100644 index 0000000..ff71f85 --- /dev/null +++ b/src/network/access/qnetworkrequest_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREQUEST_P_H +#define QNETWORKREQUEST_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 "qnetworkrequest.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qlist.h" +#include "QtCore/qhash.h" +#include "QtCore/qshareddata.h" + +QT_BEGIN_NAMESPACE + +// this is the common part between QNetworkRequestPrivate and QNetworkReplyPrivate +class QNetworkHeadersPrivate +{ +public: + typedef QPair<QByteArray, QByteArray> RawHeaderPair; + typedef QList<RawHeaderPair> RawHeadersList; + typedef QHash<QNetworkRequest::KnownHeaders, QVariant> CookedHeadersMap; + typedef QHash<QNetworkRequest::Attribute, QVariant> AttributesMap; + + RawHeadersList rawHeaders; + CookedHeadersMap cookedHeaders; + AttributesMap attributes; + + RawHeadersList::ConstIterator findRawHeader(const QByteArray &key) const; + QList<QByteArray> rawHeadersKeys() const; + void setRawHeader(const QByteArray &key, const QByteArray &value); + void setAllRawHeaders(const RawHeadersList &list); + void setCookedHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); + + static QDateTime fromHttpDate(const QByteArray &value); + static QByteArray toHttpDate(const QDateTime &dt); + +private: + void setRawHeaderInternal(const QByteArray &key, const QByteArray &value); + void parseAndSetHeader(const QByteArray &key, const QByteArray &value); +}; + +Q_DECLARE_TYPEINFO(QNetworkHeadersPrivate::RawHeaderPair, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + + +#endif |