summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 09:34:13 (GMT)
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 09:34:13 (GMT)
commit67ad0519fd165acee4a4d2a94fa502e9e4847bd0 (patch)
tree1dbf50b3dff8d5ca7e9344733968c72704eb15ff /src/network/access
downloadQt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.zip
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.gz
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.bz2
Long live Qt!
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/access.pri53
-rw-r--r--src/network/access/qabstractnetworkcache.cpp532
-rw-r--r--src/network/access/qabstractnetworkcache.h141
-rw-r--r--src/network/access/qabstractnetworkcache_p.h66
-rw-r--r--src/network/access/qftp.cpp2407
-rw-r--r--src/network/access/qftp.h180
-rw-r--r--src/network/access/qhttp.cpp3120
-rw-r--r--src/network/access/qhttp.h311
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp2464
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h290
-rw-r--r--src/network/access/qnetworkaccessbackend.cpp318
-rw-r--r--src/network/access/qnetworkaccessbackend_p.h202
-rw-r--r--src/network/access/qnetworkaccesscache.cpp379
-rw-r--r--src/network/access/qnetworkaccesscache_p.h127
-rw-r--r--src/network/access/qnetworkaccesscachebackend.cpp143
-rw-r--r--src/network/access/qnetworkaccesscachebackend_p.h86
-rw-r--r--src/network/access/qnetworkaccessdatabackend.cpp150
-rw-r--r--src/network/access/qnetworkaccessdatabackend_p.h82
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend.cpp346
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend_p.h111
-rw-r--r--src/network/access/qnetworkaccessfilebackend.cpp270
-rw-r--r--src/network/access/qnetworkaccessfilebackend_p.h95
-rw-r--r--src/network/access/qnetworkaccessftpbackend.cpp441
-rw-r--r--src/network/access/qnetworkaccessftpbackend_p.h126
-rw-r--r--src/network/access/qnetworkaccesshttpbackend.cpp1052
-rw-r--r--src/network/access/qnetworkaccesshttpbackend_p.h140
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp961
-rw-r--r--src/network/access/qnetworkaccessmanager.h129
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h121
-rw-r--r--src/network/access/qnetworkcookie.cpp932
-rw-r--r--src/network/access/qnetworkcookie.h141
-rw-r--r--src/network/access/qnetworkcookie_p.h99
-rw-r--r--src/network/access/qnetworkdiskcache.cpp666
-rw-r--r--src/network/access/qnetworkdiskcache.h94
-rw-r--r--src/network/access/qnetworkdiskcache_p.h122
-rw-r--r--src/network/access/qnetworkreply.cpp691
-rw-r--r--src/network/access/qnetworkreply.h171
-rw-r--r--src/network/access/qnetworkreply_p.h83
-rw-r--r--src/network/access/qnetworkreplyimpl.cpp598
-rw-r--r--src/network/access/qnetworkreplyimpl_p.h181
-rw-r--r--src/network/access/qnetworkrequest.cpp803
-rw-r--r--src/network/access/qnetworkrequest.h131
-rw-r--r--src/network/access/qnetworkrequest_p.h96
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, &currentChunkSize);
+ 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, &copy };
+ 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