summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/access.pri70
-rw-r--r--src/network/access/qabstractnetworkcache.cpp536
-rw-r--r--src/network/access/qabstractnetworkcache.h141
-rw-r--r--src/network/access/qabstractnetworkcache_p.h66
-rw-r--r--src/network/access/qftp.cpp2437
-rw-r--r--src/network/access/qftp.h180
-rw-r--r--src/network/access/qhttp.cpp3155
-rw-r--r--src/network/access/qhttp.h315
-rw-r--r--src/network/access/qhttpmultipart.cpp548
-rw-r--r--src/network/access/qhttpmultipart.h119
-rw-r--r--src/network/access/qhttpmultipart_p.h182
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp1010
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h230
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp1162
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h190
-rw-r--r--src/network/access/qhttpnetworkheader.cpp134
-rw-r--r--src/network/access/qhttpnetworkheader_p.h111
-rw-r--r--src/network/access/qhttpnetworkreply.cpp951
-rw-r--r--src/network/access/qhttpnetworkreply_p.h266
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp325
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h163
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp579
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h288
-rw-r--r--src/network/access/qnetworkaccessauthenticationmanager.cpp297
-rw-r--r--src/network/access/qnetworkaccessauthenticationmanager_p.h107
-rw-r--r--src/network/access/qnetworkaccessbackend.cpp382
-rw-r--r--src/network/access/qnetworkaccessbackend_p.h230
-rw-r--r--src/network/access/qnetworkaccesscache.cpp379
-rw-r--r--src/network/access/qnetworkaccesscache_p.h130
-rw-r--r--src/network/access/qnetworkaccesscachebackend.cpp146
-rw-r--r--src/network/access/qnetworkaccesscachebackend_p.h84
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend.cpp284
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend_p.h113
-rw-r--r--src/network/access/qnetworkaccessfilebackend.cpp276
-rw-r--r--src/network/access/qnetworkaccessfilebackend_p.h97
-rw-r--r--src/network/access/qnetworkaccessftpbackend.cpp382
-rw-r--r--src/network/access/qnetworkaccessftpbackend_p.h122
-rw-r--r--src/network/access/qnetworkaccesshttpbackend.cpp1191
-rw-r--r--src/network/access/qnetworkaccesshttpbackend_p.h169
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp1299
-rw-r--r--src/network/access/qnetworkaccessmanager.h177
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h164
-rw-r--r--src/network/access/qnetworkcookie.cpp1052
-rw-r--r--src/network/access/qnetworkcookie.h124
-rw-r--r--src/network/access/qnetworkcookie_p.h100
-rw-r--r--src/network/access/qnetworkcookiejar.cpp346
-rw-r--r--src/network/access/qnetworkcookiejar.h81
-rw-r--r--src/network/access/qnetworkcookiejar_p.h74
-rw-r--r--src/network/access/qnetworkcookiejartlds_p.h6481
-rw-r--r--src/network/access/qnetworkcookiejartlds_p.h.INFO17
-rw-r--r--src/network/access/qnetworkdiskcache.cpp728
-rw-r--r--src/network/access/qnetworkdiskcache.h97
-rw-r--r--src/network/access/qnetworkdiskcache_p.h129
-rw-r--r--src/network/access/qnetworkreply.cpp795
-rw-r--r--src/network/access/qnetworkreply.h180
-rw-r--r--src/network/access/qnetworkreply_p.h84
-rw-r--r--src/network/access/qnetworkreplydataimpl.cpp148
-rw-r--r--src/network/access/qnetworkreplydataimpl_p.h98
-rw-r--r--src/network/access/qnetworkreplyfileimpl.cpp189
-rw-r--r--src/network/access/qnetworkreplyfileimpl_p.h98
-rw-r--r--src/network/access/qnetworkreplyimpl.cpp1087
-rw-r--r--src/network/access/qnetworkreplyimpl_p.h238
-rw-r--r--src/network/access/qnetworkrequest.cpp1032
-rw-r--r--src/network/access/qnetworkrequest.h158
-rw-r--r--src/network/access/qnetworkrequest_p.h99
65 files changed, 32622 insertions, 0 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri
new file mode 100644
index 0000000000..5ead3ad37f
--- /dev/null
+++ b/src/network/access/access.pri
@@ -0,0 +1,70 @@
+# Qt network access module
+
+HEADERS += \
+ access/qftp.h \
+ access/qhttp.h \
+ access/qhttpnetworkheader_p.h \
+ access/qhttpnetworkrequest_p.h \
+ access/qhttpnetworkreply_p.h \
+ access/qhttpnetworkconnection_p.h \
+ access/qhttpnetworkconnectionchannel_p.h \
+ access/qnetworkaccessauthenticationmanager_p.h \
+ access/qnetworkaccessmanager.h \
+ access/qnetworkaccessmanager_p.h \
+ access/qnetworkaccesscache_p.h \
+ access/qnetworkaccessbackend_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/qnetworkcookiejar.h \
+ access/qnetworkcookiejar_p.h \
+ access/qnetworkcookiejartlds_p.h \
+ access/qnetworkrequest.h \
+ access/qnetworkrequest_p.h \
+ access/qnetworkreply.h \
+ access/qnetworkreply_p.h \
+ access/qnetworkreplyimpl_p.h \
+ access/qnetworkreplydataimpl_p.h \
+ access/qnetworkreplyfileimpl_p.h \
+ access/qabstractnetworkcache_p.h \
+ access/qabstractnetworkcache.h \
+ access/qnetworkdiskcache_p.h \
+ access/qnetworkdiskcache.h \
+ access/qhttpthreaddelegate_p.h \
+ access/qhttpmultipart.h \
+ access/qhttpmultipart_p.h
+
+SOURCES += \
+ access/qftp.cpp \
+ access/qhttp.cpp \
+ access/qhttpnetworkheader.cpp \
+ access/qhttpnetworkrequest.cpp \
+ access/qhttpnetworkreply.cpp \
+ access/qhttpnetworkconnection.cpp \
+ access/qhttpnetworkconnectionchannel.cpp \
+ access/qnetworkaccessauthenticationmanager.cpp \
+ access/qnetworkaccessmanager.cpp \
+ access/qnetworkaccesscache.cpp \
+ access/qnetworkaccessbackend.cpp \
+ access/qnetworkaccessdebugpipebackend.cpp \
+ access/qnetworkaccessfilebackend.cpp \
+ access/qnetworkaccesscachebackend.cpp \
+ access/qnetworkaccessftpbackend.cpp \
+ access/qnetworkaccesshttpbackend.cpp \
+ access/qnetworkcookie.cpp \
+ access/qnetworkcookiejar.cpp \
+ access/qnetworkrequest.cpp \
+ access/qnetworkreply.cpp \
+ access/qnetworkreplyimpl.cpp \
+ access/qnetworkreplydataimpl.cpp \
+ access/qnetworkreplyfileimpl.cpp \
+ access/qabstractnetworkcache.cpp \
+ access/qnetworkdiskcache.cpp \
+ access/qhttpthreaddelegate.cpp \
+ access/qhttpmultipart.cpp
+
+include($$PWD/../../3rdparty/zlib_dependency.pri)
diff --git a/src/network/access/qabstractnetworkcache.cpp b/src/network/access/qabstractnetworkcache.cpp
new file mode 100644
index 0000000000..de3fcc3286
--- /dev/null
+++ b/src/network/access/qabstractnetworkcache.cpp
@@ -0,0 +1,536 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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;
+}
+
+/*!
+ \since 4.6
+
+ Returns all the attributes stored with this cache item.
+
+ \sa setAttributes(), QNetworkRequest::Attribute
+*/
+QNetworkCacheMetaData::AttributesMap QNetworkCacheMetaData::attributes() const
+{
+ return d->attributes;
+}
+
+/*!
+ \since 4.6
+
+ 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 {Serializing Qt Data Types}
+*/
+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 {Serializing Qt Data Types}
+*/
+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 0000000000..d9091d9409
--- /dev/null
+++ b/src/network/access/qabstractnetworkcache.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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 0000000000..aba95213b7
--- /dev/null
+++ b/src/network/access/qabstractnetworkcache_p.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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 0000000000..4ff45babaa
--- /dev/null
+++ b/src/network/access/qftp.cpp
@@ -0,0 +1,2437 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#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 = 0;
+ }
+ socket = new QTcpSocket(this);
+#ifndef QT_NO_BEARERMANAGEMENT
+ //copy network session down to the socket
+ socket->setProperty("_q_networksession", property("_q_networksession"));
+#endif
+ 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)
+{
+#ifndef QT_NO_BEARERMANAGEMENT
+ //copy network session down to the socket
+ listener.setProperty("_q_networksession", property("_q_networksession"));
+#endif
+ 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 = qMin(maxlen, qint64(bytesFromSocket.size()));
+ memcpy(data, bytesFromSocket.data(), read);
+ bytesFromSocket.remove(0, read);
+ }
+
+ 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);
+#ifndef QT_NO_BEARERMANAGEMENT
+ //copy network session down to the socket & DTP
+ commandSocket.setProperty("_q_networksession", property("_q_networksession"));
+ dtp.setProperty("_q_networksession", property("_q_networksession"));
+#endif
+ 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
+ // try to improve performance by setting TCP_NODELAY
+ commandSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
+
+ 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 network
+ \inmodule QtNetwork
+
+
+ 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 the Qt
+ 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 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)
+{
+ QStringList cmds;
+ cmds << host;
+ cmds << QString::number((uint)port);
+ int id = d_func()->addCommand(new QFtpCommand(ConnectToHost, cmds));
+ d_func()->pi.transferConnectionExtended = true;
+ return id;
+}
+
+/*!
+ 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)
+{
+ int id = d_func()->addCommand(new QFtpCommand(SetTransferMode, QStringList()));
+ d_func()->pi.transferConnectionExtended = true;
+ d_func()->transferMode = mode;
+ return id;
+}
+
+/*!
+ 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
+ QNetworkAccessManager 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) {
+#ifndef QT_NO_BEARERMANAGEMENT
+ //copy network session down to the PI
+ pi.setProperty("_q_networksession", q->property("_q_networksession"));
+#endif
+ 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);
+
+ if (pending.isEmpty()) {
+ qWarning("QFtpPrivate::_q_piError was called without pending command!");
+ return;
+ }
+
+ 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 0000000000..bee472c3bb
--- /dev/null
+++ b/src/network/access/qftp.h
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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 0000000000..291716b174
--- /dev/null
+++ b/src/network/access/qhttp.cpp
@@ -0,0 +1,3155 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#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
+
+#ifndef QT_NO_HTTP
+
+QT_BEGIN_NAMESPACE
+
+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);
+#ifndef QT_NO_OPENSSL
+ void _q_slotEncryptedBytesWritten(qint64 numBytes);
+#endif
+ 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);
+
+ void postMoreData();
+
+ 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
+ \obsolete
+ \brief The QHttpHeader class contains header information for HTTP.
+
+ \ingroup network
+ \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()
+{
+}
+
+/*!
+ 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 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
+ \obsolete
+ \brief The QHttpResponseHeader class contains response header information for HTTP.
+
+ \ingroup network
+ \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;
+}
+
+/*! \internal
+*/
+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
+ \obsolete
+ \brief The QHttpRequestHeader class contains request header information for HTTP.
+
+ \ingroup network
+ \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;
+}
+
+/*! \internal
+*/
+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
+ \obsolete
+ \reentrant
+
+ \brief The QHttp class provides an implementation of the HTTP protocol.
+
+ \ingroup network
+ \inmodule QtNetwork
+
+
+ This class provides a direct interface to HTTP that allows you to
+ download and upload data with the HTTP protocol.
+ However, for new applications, it is
+ recommended to use QNetworkAccessManager and QNetworkReply, as
+ those classes possess a simpler, yet more powerful API
+ and a more modern protocol implementation.
+
+ 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 Qt website (i.e., the URL \c http://qt.nokia.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 for 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.
+
+ \note setProxy() has to be called before setHost() for it to take effect.
+ If setProxy() is called after setHost(), then it will not apply until after
+ setHost() is called again.
+
+ \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://example.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://example.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://example.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 caching 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();
+}
+
+#ifndef QT_NO_OPENSSL
+void QHttpPrivate::_q_slotEncryptedBytesWritten(qint64 written)
+{
+ Q_UNUSED(written);
+ postMoreData();
+}
+#endif
+
+void QHttpPrivate::_q_slotBytesWritten(qint64 written)
+{
+ Q_Q(QHttp);
+ bytesDone += written;
+ emit q->dataSendProgress(bytesDone, bytesTotal);
+ postMoreData();
+}
+
+// Send the POST data
+void QHttpPrivate::postMoreData()
+{
+ if (pendingPost)
+ return;
+
+ if (!postDevice)
+ return;
+
+ // the following is backported code from Qt 4.6 QNetworkAccessManager.
+ // We also have to check the encryptedBytesToWrite() if it is an SSL socket.
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ // if it is really an ssl socket, check more than just bytesToWrite()
+ if ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0)) == 0) {
+#else
+ if (socket->bytesToWrite() == 0) {
+#endif
+ 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(QNetworkProxy,QAuthenticator*)),
+ q, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
+#endif
+
+#ifndef QT_NO_OPENSSL
+ if (qobject_cast<QSslSocket *>(socket)) {
+ QObject::connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
+ q, SIGNAL(sslErrors(QList<QSslError>)));
+ QObject::connect(socket, SIGNAL(encryptedBytesWritten(qint64)),
+ q, SLOT(_q_slotEncryptedBytesWritten(qint64)));
+ }
+#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 0000000000..d6156f5941
--- /dev/null
+++ b/src/network/access/qhttp.h
@@ -0,0 +1,315 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTP_H
+#define QHTTP_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qpair.h>
+#include <QtCore/qscopedpointer.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);
+ QScopedPointer<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))
+#ifndef QT_NO_OPENSSL
+ Q_PRIVATE_SLOT(d_func(), void _q_slotEncryptedBytesWritten(qint64 numBytes))
+#endif
+ 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/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp
new file mode 100644
index 0000000000..640f9ead01
--- /dev/null
+++ b/src/network/access/qhttpmultipart.cpp
@@ -0,0 +1,548 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpmultipart.h"
+#include "qhttpmultipart_p.h"
+#include "QtCore/qdatetime.h" // for initializing the random number generator with QTime
+#include "QtCore/qmutex.h"
+#include "QtCore/qthreadstorage.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QHttpPart
+ \brief The QHttpPart class holds a body part to be used inside a
+ HTTP multipart MIME message.
+ \since 4.8
+
+ \ingroup network
+ \inmodule QtNetwork
+
+ The QHttpPart class holds a body part to be used inside a HTTP
+ multipart MIME message (which is represented by the QHttpMultiPart class).
+ A QHttpPart consists of a header block
+ and a data block, which are separated by each other by two
+ consecutive new lines. An example for one part would be:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 0
+
+ For setting headers, use setHeader() and setRawHeader(), which behave
+ exactly like QNetworkRequest::setHeader() and QNetworkRequest::setRawHeader().
+
+ For reading small pieces of data, use setBody(); for larger data blocks
+ like e.g. images, use setBodyDevice(). The latter method saves memory by
+ not copying the data internally, but reading directly from the device.
+ This means that the device must be opened and readable at the moment when
+ the multipart message containing the body part is sent on the network via
+ QNetworkAccessManager::post().
+
+ To construct a QHttpPart with a small body, consider the following snippet
+ (this produces the data shown in the example above):
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 1
+
+ To construct a QHttpPart reading from a device (e.g. a file), the following
+ can be applied:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttppart.cpp 2
+
+ Be aware that QHttpPart does not take ownership of the device when set, so
+ it is the developer's responsibility to destroy it when it is not needed anymore.
+ A good idea might be to set the multipart message as parent object for the device,
+ as documented at the documentation for QHttpMultiPart.
+
+ \sa QHttpMultiPart, QNetworkAccessManager
+*/
+
+
+/*!
+ Constructs an empty QHttpPart object.
+*/
+QHttpPart::QHttpPart() : d(new QHttpPartPrivate)
+{
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QHttpPart::QHttpPart(const QHttpPart &other) : d(other.d)
+{
+}
+
+/*!
+ Destroys this QHttpPart.
+*/
+QHttpPart::~QHttpPart()
+{
+ d = 0;
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QHttpPart &QHttpPart::operator=(const QHttpPart &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ Returns true if this object is the same as \a other (i.e., if they
+ have the same headers and body).
+
+ \sa operator!=()
+*/
+bool QHttpPart::operator==(const QHttpPart &other) const
+{
+ return d == other.d || *d == *other.d;
+}
+
+/*!
+ \fn bool QHttpPart::operator!=(const QHttpPart &other) const
+
+ Returns true if this object is not the same as \a other.
+
+ \sa operator==()
+*/
+
+/*!
+ Sets the value of the known header \a header to be \a value,
+ overriding any previously set headers.
+
+ \sa QNetworkRequest::KnownHeaders, setRawHeader(), QNetworkRequest::setHeader()
+*/
+void QHttpPart::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)
+{
+ d->setCookedHeader(header, value);
+}
+
+/*!
+ Sets the header \a headerName to be of value \a headerValue. If \a
+ headerName corresponds to a known header (see
+ QNetworkRequest::KnownHeaders), the raw format will be parsed and
+ the corresponding "cooked" header will be set as well.
+
+ Note: setting the same header twice overrides the previous
+ setting. To accomplish the behaviour of multiple HTTP headers of
+ the same name, you should concatenate the two values, separating
+ them with a comma (",") and set one single raw header.
+
+ \sa QNetworkRequest::KnownHeaders, setHeader(), QNetworkRequest::setRawHeader()
+*/
+void QHttpPart::setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
+{
+ d->setRawHeader(headerName, headerValue);
+}
+
+/*!
+ Sets the body of this MIME part to \a body. The body set with this method
+ will be used unless the device is set via setBodyDevice(). For a large
+ amount of data (e.g. an image), use setBodyDevice(), which will not copy
+ the data internally.
+
+ \sa setBodyDevice()
+*/
+void QHttpPart::setBody(const QByteArray &body)
+{
+ d->setBody(body);
+}
+
+/*!
+ Sets the device to read the content from to \a device. For large amounts of data
+ this method should be preferred over setBody(),
+ because the content is not copied when using this method, but read
+ directly from the device.
+ \a device must be open and readable. QHttpPart does not take ownership
+ of \a device, i.e. the device must be closed and destroyed if necessary.
+ if \a device is sequential (e.g. sockets, but not files),
+ QNetworkAccessManager::post() should be called after \a device has
+ emitted finished().
+ For unsetting the device and using data set via setBody(), use
+ "setBodyDevice(0)".
+
+ \sa setBody(), QNetworkAccessManager::post()
+ */
+void QHttpPart::setBodyDevice(QIODevice *device)
+{
+ d->setBodyDevice(device);
+}
+
+
+
+/*!
+ \class QHttpMultiPart
+ \brief The QHttpMultiPart class resembles a MIME multipart message to be sent over HTTP.
+ \since 4.8
+
+ \ingroup network
+ \inmodule QtNetwork
+
+ The QHttpMultiPart resembles a MIME multipart message, as described in RFC 2046,
+ which is to be sent over HTTP.
+ A multipart message consists of an arbitrary number of body parts (see QHttpPart),
+ which are separated by a unique boundary. The boundary of the QHttpMultiPart is
+ constructed with the string "boundary_.oOo._" followed by random characters,
+ and provides enough uniqueness to make sure it does not occur inside the parts itself.
+ If desired, the boundary can still be set via setBoundary().
+
+ As an example, consider the following code snippet, which constructs a multipart
+ message containing a text part followed by an image part:
+
+ \snippet doc/src/snippets/code/src_network_access_qhttpmultipart.cpp 0
+
+ \sa QHttpPart, QNetworkAccessManager::post()
+*/
+
+/*!
+ \enum QHttpMultiPart::ContentType
+
+ List of known content types for a multipart subtype as described
+ in RFC 2046 and others.
+
+ \value MixedType corresponds to the "multipart/mixed" subtype,
+ meaning the body parts are independent of each other, as described
+ in RFC 2046.
+
+ \value RelatedType corresponds to the "multipart/related" subtype,
+ meaning the body parts are related to each other, as described in RFC 2387.
+
+ \value FormDataType corresponds to the "multipart/form-data"
+ subtype, meaning the body parts contain form elements, as described in RFC 2388.
+
+ \value AlternativeType corresponds to the "multipart/alternative"
+ subtype, meaning the body parts are alternative representations of
+ the same information, as described in RFC 2046.
+
+ \sa setContentType()
+*/
+
+/*!
+ Constructs a QHttpMultiPart with content type MixedType and sets
+ parent as the parent object.
+
+ \sa QHttpMultiPart::ContentType
+*/
+QHttpMultiPart::QHttpMultiPart(QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
+{
+ Q_D(QHttpMultiPart);
+ d->contentType = MixedType;
+}
+
+/*!
+ Constructs a QHttpMultiPart with content type \a contentType and
+ sets parent as the parent object.
+
+ \sa QHttpMultiPart::ContentType
+*/
+QHttpMultiPart::QHttpMultiPart(QHttpMultiPart::ContentType contentType, QObject *parent) : QObject(*new QHttpMultiPartPrivate, parent)
+{
+ Q_D(QHttpMultiPart);
+ d->contentType = contentType;
+}
+
+/*!
+ Destroys the multipart.
+*/
+QHttpMultiPart::~QHttpMultiPart()
+{
+}
+
+/*!
+ Appends \a httpPart to this multipart.
+*/
+void QHttpMultiPart::append(const QHttpPart &httpPart)
+{
+ d_func()->parts.append(httpPart);
+}
+
+/*!
+ Sets the content type to \a contentType. The content type will be used
+ in the HTTP header section when sending the multipart message via
+ QNetworkAccessManager::post().
+ In case you want to use a multipart subtype not contained in
+ QHttpMultiPart::ContentType,
+ you can add the "Content-Type" header field to the QNetworkRequest
+ by hand, and then use this request together with the multipart
+ message for posting.
+
+ \sa QHttpMultiPart::ContentType, QNetworkAccessManager::post()
+*/
+void QHttpMultiPart::setContentType(QHttpMultiPart::ContentType contentType)
+{
+ d_func()->contentType = contentType;
+}
+
+/*!
+ returns the boundary.
+
+ \sa setBoundary()
+*/
+QByteArray QHttpMultiPart::boundary() const
+{
+ return d_func()->boundary;
+}
+
+/*!
+ Sets the boundary to \a boundary.
+
+ Usually, you do not need to generate a boundary yourself; upon construction
+ the boundary is initiated with the string "boundary_.oOo._" followed by random
+ characters, and provides enough uniqueness to make sure it does not occur
+ inside the parts itself.
+
+ \sa boundary()
+*/
+void QHttpMultiPart::setBoundary(const QByteArray &boundary)
+{
+ d_func()->boundary = boundary;
+}
+
+
+
+// ------------------------------------------------------------------
+// ----------- implementations of private classes: ------------------
+// ------------------------------------------------------------------
+
+
+
+qint64 QHttpPartPrivate::bytesAvailable() const
+{
+ checkHeaderCreated();
+ qint64 bytesAvailable = header.count();
+ if (bodyDevice) {
+ bytesAvailable += bodyDevice->bytesAvailable() - readPointer;
+ } else {
+ bytesAvailable += body.count() - readPointer;
+ }
+ // the device might have closed etc., so make sure we do not return a negative value
+ return qMax(bytesAvailable, (qint64) 0);
+}
+
+qint64 QHttpPartPrivate::readData(char *data, qint64 maxSize)
+{
+ checkHeaderCreated();
+ qint64 bytesRead = 0;
+ qint64 headerDataCount = header.count();
+
+ // read header if it has not been read yet
+ if (readPointer < headerDataCount) {
+ bytesRead = qMin(headerDataCount - readPointer, maxSize);
+ const char *headerData = header.constData();
+ memcpy(data, headerData + readPointer, bytesRead);
+ readPointer += bytesRead;
+ }
+ // read content if there is still space
+ if (bytesRead < maxSize) {
+ if (bodyDevice) {
+ qint64 dataBytesRead = bodyDevice->read(data + bytesRead, maxSize - bytesRead);
+ if (dataBytesRead == -1)
+ return -1;
+ bytesRead += dataBytesRead;
+ readPointer += dataBytesRead;
+ } else {
+ qint64 contentBytesRead = qMin(body.count() - readPointer + headerDataCount, maxSize - bytesRead);
+ const char *contentData = body.constData();
+ // if this method is called several times, we need to find the
+ // right offset in the content ourselves:
+ memcpy(data + bytesRead, contentData + readPointer - headerDataCount, contentBytesRead);
+ bytesRead += contentBytesRead;
+ readPointer += contentBytesRead;
+ }
+ }
+ return bytesRead;
+}
+
+qint64 QHttpPartPrivate::size() const
+{
+ checkHeaderCreated();
+ qint64 size = header.count();
+ if (bodyDevice) {
+ size += bodyDevice->size();
+ } else {
+ size += body.count();
+ }
+ return size;
+}
+
+bool QHttpPartPrivate::reset()
+{
+ bool ret = true;
+ if (bodyDevice)
+ if (!bodyDevice->reset())
+ ret = false;
+ readPointer = 0;
+ return ret;
+}
+void QHttpPartPrivate::checkHeaderCreated() const
+{
+ if (!headerCreated) {
+ // copied from QHttpNetworkRequestPrivate::header() and adapted
+ QList<QPair<QByteArray, QByteArray> > fields = allRawHeaders();
+ QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
+ for (; it != fields.constEnd(); ++it)
+ header += it->first + ": " + it->second + "\r\n";
+ header += "\r\n";
+ headerCreated = true;
+ }
+}
+
+Q_GLOBAL_STATIC(QThreadStorage<bool *>, seedCreatedStorage);
+
+QHttpMultiPartPrivate::QHttpMultiPartPrivate() : contentType(QHttpMultiPart::MixedType), device(new QHttpMultiPartIODevice(this))
+{
+ if (!seedCreatedStorage()->hasLocalData()) {
+ qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) ^ reinterpret_cast<quintptr>(this));
+ seedCreatedStorage()->setLocalData(new bool(true));
+ }
+
+ boundary = QByteArray("boundary_.oOo._")
+ + QByteArray::number(qrand()).toBase64()
+ + QByteArray::number(qrand()).toBase64()
+ + QByteArray::number(qrand()).toBase64();
+
+ // boundary must not be longer than 70 characters, see RFC 2046, section 5.1.1
+ if (boundary.count() > 70)
+ boundary = boundary.left(70);
+}
+
+qint64 QHttpMultiPartIODevice::size() const
+{
+ // if not done yet, we calculate the size and the offsets of each part,
+ // including boundary (needed later in readData)
+ if (deviceSize == -1) {
+ qint64 currentSize = 0;
+ qint64 boundaryCount = multiPart->boundary.count();
+ for (int a = 0; a < multiPart->parts.count(); a++) {
+ partOffsets.append(currentSize);
+ // 4 additional bytes for the "--" before and the "\r\n" after the boundary,
+ // and 2 bytes for the "\r\n" after the content
+ currentSize += boundaryCount + 4 + multiPart->parts.at(a).d->size() + 2;
+ }
+ currentSize += boundaryCount + 4; // size for ending boundary and 2 beginning and ending dashes
+ deviceSize = currentSize;
+ }
+ return deviceSize;
+}
+
+bool QHttpMultiPartIODevice::isSequential() const
+{
+ for (int a = 0; a < multiPart->parts.count(); a++) {
+ QIODevice *device = multiPart->parts.at(a).d->bodyDevice;
+ // we are sequential if any of the bodyDevices of our parts are sequential;
+ // when reading from a byte array, we are not sequential
+ if (device && device->isSequential())
+ return true;
+ }
+ return false;
+}
+
+bool QHttpMultiPartIODevice::reset()
+{
+ for (int a = 0; a < multiPart->parts.count(); a++)
+ if (!multiPart->parts[a].d->reset())
+ return false;
+ return true;
+}
+qint64 QHttpMultiPartIODevice::readData(char *data, qint64 maxSize)
+{
+ qint64 bytesRead = 0, index = 0;
+
+ // skip the parts we have already read
+ while (index < multiPart->parts.count() &&
+ readPointer >= partOffsets.at(index) + multiPart->parts.at(index).d->size())
+ index++;
+
+ // read the data
+ while (bytesRead < maxSize && index < multiPart->parts.count()) {
+
+ // check whether we need to read the boundary of the current part
+ QByteArray boundaryData = "--" + multiPart->boundary + "\r\n";
+ qint64 boundaryCount = boundaryData.count();
+ qint64 partIndex = readPointer - partOffsets.at(index);
+ if (partIndex < boundaryCount) {
+ qint64 boundaryBytesRead = qMin(boundaryCount - partIndex, maxSize - bytesRead);
+ memcpy(data + bytesRead, boundaryData.constData() + partIndex, boundaryBytesRead);
+ bytesRead += boundaryBytesRead;
+ readPointer += boundaryBytesRead;
+ partIndex += boundaryBytesRead;
+ }
+
+ // check whether we need to read the data of the current part
+ if (bytesRead < maxSize && partIndex >= boundaryCount && partIndex < boundaryCount + multiPart->parts.at(index).d->size()) {
+ qint64 dataBytesRead = multiPart->parts[index].d->readData(data + bytesRead, maxSize - bytesRead);
+ if (dataBytesRead == -1)
+ return -1;
+ bytesRead += dataBytesRead;
+ readPointer += dataBytesRead;
+ partIndex += dataBytesRead;
+ }
+
+ // check whether we need to read the ending CRLF of the current part
+ if (bytesRead < maxSize && partIndex >= boundaryCount + multiPart->parts.at(index).d->size()) {
+ if (bytesRead == maxSize - 1)
+ return bytesRead;
+ memcpy(data + bytesRead, "\r\n", 2);
+ bytesRead += 2;
+ readPointer += 2;
+ index++;
+ }
+ }
+ // check whether we need to return the final boundary
+ if (bytesRead < maxSize && index == multiPart->parts.count()) {
+ QByteArray finalBoundary = "--" + multiPart->boundary + "--";
+ qint64 boundaryIndex = readPointer + finalBoundary.count() - size();
+ qint64 lastBoundaryBytesRead = qMin(finalBoundary.count() - boundaryIndex, maxSize - bytesRead);
+ memcpy(data + bytesRead, finalBoundary.constData() + boundaryIndex, lastBoundaryBytesRead);
+ bytesRead += lastBoundaryBytesRead;
+ readPointer += lastBoundaryBytesRead;
+ }
+ return bytesRead;
+}
+
+qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
+{
+ Q_UNUSED(data);
+ Q_UNUSED(maxSize);
+ return -1;
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpmultipart.h b/src/network/access/qhttpmultipart.h
new file mode 100644
index 0000000000..0a3342c6bb
--- /dev/null
+++ b/src/network/access/qhttpmultipart.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPMULTIPART_H
+#define QHTTPMULTIPART_H
+
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QByteArray>
+#include <QtNetwork/QNetworkRequest>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Network)
+
+class QHttpPartPrivate;
+class QHttpMultiPart;
+
+class Q_NETWORK_EXPORT QHttpPart
+{
+public:
+ QHttpPart();
+ QHttpPart(const QHttpPart &other);
+ ~QHttpPart();
+ QHttpPart &operator=(const QHttpPart &other);
+ bool operator==(const QHttpPart &other) const;
+ inline bool operator!=(const QHttpPart &other) const
+ { return !operator==(other); }
+
+ void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
+ void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue);
+
+ void setBody(const QByteArray &body);
+ void setBodyDevice(QIODevice *device);
+
+private:
+ QSharedDataPointer<QHttpPartPrivate> d;
+
+ friend class QHttpMultiPartIODevice;
+};
+
+class QHttpMultiPartPrivate;
+
+class Q_NETWORK_EXPORT QHttpMultiPart : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum ContentType {
+ MixedType,
+ RelatedType,
+ FormDataType,
+ AlternativeType
+ };
+
+ QHttpMultiPart(QObject *parent = 0);
+ QHttpMultiPart(ContentType contentType, QObject *parent = 0);
+ ~QHttpMultiPart();
+
+ void append(const QHttpPart &httpPart);
+
+ void setContentType(ContentType contentType);
+
+ QByteArray boundary() const;
+ void setBoundary(const QByteArray &boundary);
+
+private:
+ Q_DECLARE_PRIVATE(QHttpMultiPart)
+ Q_DISABLE_COPY(QHttpMultiPart)
+
+ friend class QNetworkAccessManager;
+ friend class QNetworkAccessManagerPrivate;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QHTTPMULTIPART_H
diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h
new file mode 100644
index 0000000000..7dc13e9a61
--- /dev/null
+++ b/src/network/access/qhttpmultipart_p.h
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPMULTIPART_P_H
+#define QHTTPMULTIPART_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtCore/qshareddata.h"
+#include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate
+#include "private/qobject_p.h"
+
+QT_BEGIN_NAMESPACE
+
+
+class QHttpPartPrivate: public QSharedData, public QNetworkHeadersPrivate
+{
+public:
+ inline QHttpPartPrivate() : bodyDevice(0), headerCreated(false), readPointer(0)
+ {
+ }
+ ~QHttpPartPrivate()
+ {
+ }
+
+
+ QHttpPartPrivate(const QHttpPartPrivate &other)
+ : QSharedData(other), QNetworkHeadersPrivate(other), body(other.body),
+ header(other.header), headerCreated(other.headerCreated), readPointer(other.readPointer)
+ {
+ bodyDevice = other.bodyDevice;
+ }
+
+ inline bool operator==(const QHttpPartPrivate &other) const
+ {
+ return rawHeaders == other.rawHeaders && body == other.body &&
+ bodyDevice == other.bodyDevice && readPointer == other.readPointer;
+ }
+
+ void setBodyDevice(QIODevice *device) {
+ bodyDevice = device;
+ readPointer = 0;
+ }
+ void setBody(const QByteArray &newBody) {
+ body = newBody;
+ readPointer = 0;
+ }
+
+ // QIODevice-style methods called by QHttpMultiPartIODevice (but this class is
+ // not a QIODevice):
+ qint64 bytesAvailable() const;
+ qint64 readData(char *data, qint64 maxSize);
+ qint64 size() const;
+ bool reset();
+
+ QByteArray body;
+ QIODevice *bodyDevice;
+
+private:
+ void checkHeaderCreated() const;
+
+ mutable QByteArray header;
+ mutable bool headerCreated;
+ qint64 readPointer;
+};
+
+
+
+class QHttpMultiPartPrivate;
+
+class Q_AUTOTEST_EXPORT QHttpMultiPartIODevice : public QIODevice
+{
+public:
+ QHttpMultiPartIODevice(QHttpMultiPartPrivate *parentMultiPart) :
+ QIODevice(), multiPart(parentMultiPart), readPointer(0), deviceSize(-1) {
+ }
+
+ ~QHttpMultiPartIODevice() {
+ }
+
+ virtual bool atEnd() const {
+ return readPointer == size();
+ }
+
+ virtual qint64 bytesAvailable() const {
+ return size() - readPointer;
+ }
+
+ virtual void close() {
+ readPointer = 0;
+ partOffsets.clear();
+ deviceSize = -1;
+ QIODevice::close();
+ }
+
+ virtual qint64 bytesToWrite() const {
+ return 0;
+ }
+
+ virtual qint64 size() const;
+ virtual bool isSequential() const;
+ virtual bool reset();
+ virtual qint64 readData(char *data, qint64 maxSize);
+ virtual qint64 writeData(const char *data, qint64 maxSize);
+
+ QHttpMultiPartPrivate *multiPart;
+ qint64 readPointer;
+ mutable QList<qint64> partOffsets;
+ mutable qint64 deviceSize;
+};
+
+
+
+class QHttpMultiPartPrivate: public QObjectPrivate
+{
+public:
+
+ QHttpMultiPartPrivate();
+
+ ~QHttpMultiPartPrivate()
+ {
+ delete device;
+ }
+
+ QList<QHttpPart> parts;
+ QByteArray boundary;
+ QHttpMultiPart::ContentType contentType;
+ QHttpMultiPartIODevice *device;
+
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QHTTPMULTIPART_P_H
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
new file mode 100644
index 0000000000..83156c6a35
--- /dev/null
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -0,0 +1,1010 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qabstractsocket_p.h>
+#include "qhttpnetworkconnection_p.h"
+#include "qhttpnetworkconnectionchannel_p.h"
+#include "private/qnoncontiguousbytedevice_p.h"
+#include <private/qnetworkrequest_p.h>
+#include <private/qobject_p.h>
+#include <private/qauthenticator_p.h>
+#include <qnetworkproxy.h>
+#include <qauthenticator.h>
+
+#include <qbuffer.h>
+#include <qpair.h>
+#include <qhttp.h>
+#include <qdebug.h>
+
+#ifndef QT_NO_HTTP
+
+#ifndef QT_NO_OPENSSL
+# include <private/qsslsocket_p.h>
+# include <QtNetwork/qsslkey.h>
+# include <QtNetwork/qsslcipher.h>
+# include <QtNetwork/qsslconfiguration.h>
+#endif
+
+
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_OS_SYMBIAN
+const int QHttpNetworkConnectionPrivate::defaultChannelCount = 3;
+#else
+const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6;
+#endif
+
+// The pipeline length. So there will be 4 requests in flight.
+const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3;
+// Only re-fill the pipeline if there's defaultRePipelineLength slots free in the pipeline.
+// This means that there are 2 requests in flight and 2 slots free that will be re-filled.
+const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2;
+
+
+QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt)
+: state(RunningState),
+ hostName(hostName), port(port), encrypt(encrypt),
+ channelCount(defaultChannelCount)
+#ifndef QT_NO_NETWORKPROXY
+ , networkProxy(QNetworkProxy::NoProxy)
+#endif
+{
+ channels = new QHttpNetworkConnectionChannel[channelCount];
+}
+
+QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt)
+: state(RunningState),
+ hostName(hostName), port(port), encrypt(encrypt),
+ channelCount(channelCount)
+#ifndef QT_NO_NETWORKPROXY
+ , networkProxy(QNetworkProxy::NoProxy)
+#endif
+{
+ channels = new QHttpNetworkConnectionChannel[channelCount];
+}
+
+
+
+QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
+{
+ for (int i = 0; i < channelCount; ++i) {
+ if (channels[i].socket) {
+ channels[i].socket->close();
+ delete channels[i].socket;
+ }
+ }
+ delete []channels;
+}
+
+void QHttpNetworkConnectionPrivate::init()
+{
+ Q_Q(QHttpNetworkConnection);
+ for (int i = 0; i < channelCount; i++) {
+ channels[i].setConnection(this->q_func());
+ channels[i].ssl = encrypt;
+#ifndef QT_NO_BEARERMANAGEMENT
+ //push session down to channels
+ channels[i].networkSession = networkSession;
+#endif
+ channels[i].init();
+ }
+}
+
+void QHttpNetworkConnectionPrivate::pauseConnection()
+{
+ state = PausedState;
+
+ // Disable all socket notifiers
+ for (int i = 0; i < channelCount; i++) {
+#ifndef QT_NO_OPENSSL
+ if (encrypt)
+ QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ else
+#endif
+ QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket);
+ }
+}
+
+void QHttpNetworkConnectionPrivate::resumeConnection()
+{
+ state = RunningState;
+ // Enable all socket notifiers
+ for (int i = 0; i < channelCount; i++) {
+#ifndef QT_NO_OPENSSL
+ if (encrypt)
+ QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ else
+#endif
+ QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket);
+
+ // Resume pending upload if needed
+ if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
+ QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
+ }
+
+ // queue _q_startNextRequest
+ QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
+}
+
+int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const
+{
+ for (int i = 0; i < channelCount; ++i)
+ if (channels[i].socket == socket)
+ return i;
+
+ qFatal("Called with unknown socket object.");
+ return 0;
+}
+
+qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailable(const QHttpNetworkReply &reply) const
+{
+ return reply.d_func()->responseData.byteAmount();
+}
+
+qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const
+{
+ return reply.d_func()->responseData.sizeNextBlock();
+}
+
+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
+ QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
+ if (uploadByteDevice) {
+ if (request.contentLength() != -1 && uploadByteDevice->size() != -1) {
+ // both values known, take the smaller one.
+ request.setContentLength(qMin(uploadByteDevice->size(), request.contentLength()));
+ } else if (request.contentLength() == -1 && uploadByteDevice->size() != -1) {
+ // content length not supplied by user, but the upload device knows it
+ request.setContentLength(uploadByteDevice->size());
+ } else if (request.contentLength() != -1 && uploadByteDevice->size() == -1) {
+ // everything OK, the user supplied us the contentLength
+ } else if (request.contentLength() == -1 && uploadByteDevice->size() == -1) {
+ qFatal("QHttpNetworkConnectionPrivate: Neither content-length nor upload device size were given");
+ }
+ }
+ // 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
+
+ // If the request had a accept-encoding set, we better not mess
+ // with it. If it was not set, we announce that we understand gzip
+ // and remember this fact in request.d->autoDecompress so that
+ // we can later decompress the HTTP reply if it has such an
+ // encoding.
+ 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
+ }
+
+ // some websites mandate an accept-language header and fail
+ // if it is not sent. This is a problem with the website and
+ // not with us, but we work around this by setting
+ // one always.
+ value = request.headerField("accept-language");
+ if (value.isEmpty()) {
+ QString systemLocale = QLocale::system().name().replace(QChar::fromAscii('_'),QChar::fromAscii('-'));
+ QString acceptLanguage;
+ if (systemLocale == QLatin1String("C"))
+ acceptLanguage = QString::fromAscii("en,*");
+ else if (systemLocale.startsWith(QLatin1String("en-")))
+ acceptLanguage = QString::fromAscii("%1,*").arg(systemLocale);
+ else
+ acceptLanguage = QString::fromAscii("%1,en,*").arg(systemLocale);
+ request.setHeaderField("Accept-Language", acceptLanguage.toAscii());
+ }
+
+ // 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;
+}
+
+
+
+
+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
+ reply->d_func()->eraseData();
+
+ // Clean the channel
+ channels[i].close();
+ channels[i].reply = 0;
+ channels[i].request = QHttpNetworkRequest();
+ channels[i].requeueCurrentlyPipelinedRequests();
+
+ // send the next request
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+}
+
+void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy)
+{
+ Q_ASSERT(auth);
+
+ // NTLM is a multi phase authentication. Copying credentials between authenticators would mess things up.
+ if (!isProxy && channels[fromChannel].authMethod == QAuthenticatorPrivate::Ntlm)
+ return;
+ if (isProxy && channels[fromChannel].proxyAuthMethod == QAuthenticatorPrivate::Ntlm)
+ return;
+
+
+ // 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());
+ }
+}
+
+
+// handles the authentication for one channel and eventually re-starts the other channels
+bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply,
+ bool isProxy, bool &resend)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ resend = false;
+ //create the response header to be used with QAuthenticatorPrivate.
+ QList<QPair<QByteArray, QByteArray> > fields = reply->header();
+
+ //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].proxyAuthMethod = authMethod;
+ } else {
+ auth = &channels[i].authenticator;
+ channels[i].authMethod = authMethod;
+ }
+ //proceed with the authentication.
+ if (auth->isNull())
+ auth->detach();
+ QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
+ priv->parseHttpResponse(fields, isProxy);
+
+ if (priv->phase == QAuthenticatorPrivate::Done) {
+ pauseConnection();
+ if (!isProxy) {
+ emit reply->authenticationRequired(reply->request(), auth);
+#ifndef QT_NO_NETWORKPROXY
+ } else {
+ emit reply->proxyAuthenticationRequired(networkProxy, auth);
+#endif
+ }
+ resumeConnection();
+
+ if (priv->phase != QAuthenticatorPrivate::Done) {
+ // send any pending requests
+ copyCredentials(i, auth, isProxy);
+ }
+ }
+ // - Changing values in QAuthenticator will reset the 'phase'. Therefore if it is still "Done"
+ // then nothing was filled in by the user or the cache
+ // - If withCredentials has been set to false (e.g. by QtWebKit for a cross-origin XMLHttpRequest) then
+ // we need to bail out if authentication is required.
+ if (priv->phase == QAuthenticatorPrivate::Done || !reply->request().withCredentials()) {
+ // Reset authenticator so the next request on that channel does not get messed up
+ auth = 0;
+ if (isProxy)
+ channels[i].proxyAuthenticator = QAuthenticator();
+ else
+ channels[i].authenticator = QAuthenticator();
+
+ // authentication is cancelled, send the current contents to the user.
+ emit channels[i].reply->headerChanged();
+ emit channels[i].reply->readyRead();
+ QNetworkReply::NetworkError errorCode =
+ isProxy
+ ? QNetworkReply::ProxyAuthenticationRequiredError
+ : QNetworkReply::AuthenticationRequiredError;
+ reply->d_func()->errorString = errorDetail(errorCode, socket);
+ emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
+ // ### at this point the reply could be deleted
+ socket->close();
+ 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);
+
+ // Send "Authorization" header, but not if it's NTLM and the socket is already authenticated.
+ if (channels[i].authMethod != QAuthenticatorPrivate::None) {
+ if (!(channels[i].authMethod == 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);
+ }
+ }
+ }
+
+ // Send "Proxy-Authorization" header, but not if it's NTLM and the socket is already authenticated.
+ if (channels[i].proxyAuthMethod != QAuthenticatorPrivate::None) {
+ if (!(channels[i].proxyAuthMethod == 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;
+ reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later
+ 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;
+ }
+
+ // this used to be called via invokeMethod and a QueuedConnection
+ // It is the only place _q_startNextRequest is called directly without going
+ // through the event loop using a QueuedConnection.
+ // This is dangerous because of recursion that might occur when emitting
+ // signals as DirectConnection from this code path. Therefore all signal
+ // emissions that can come out from this code path need to
+ // be QueuedConnection.
+ // We are currently trying to fine-tune this.
+ _q_startNextRequest();
+
+
+ return reply;
+}
+
+void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ QHttpNetworkRequest request = pair.first;
+ 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);
+}
+
+bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket)
+{
+ Q_ASSERT(socket);
+
+ int i = indexOf(socket);
+
+ if (!highPriorityQueue.isEmpty()) {
+ // remove from queue before sendRequest! else we might pipeline the same request again
+ HttpMessagePair messagePair = highPriorityQueue.takeLast();
+ if (!messagePair.second->d_func()->requestIsPrepared)
+ prepareRequest(messagePair);
+ channels[i].request = messagePair.first;
+ channels[i].reply = messagePair.second;
+ return true;
+ }
+
+ if (!lowPriorityQueue.isEmpty()) {
+ // remove from queue before sendRequest! else we might pipeline the same request again
+ HttpMessagePair messagePair = lowPriorityQueue.takeLast();
+ if (!messagePair.second->d_func()->requestIsPrepared)
+ prepareRequest(messagePair);
+ channels[i].request = messagePair.first;
+ channels[i].reply = messagePair.second;
+ return true;
+ }
+ return false;
+}
+
+// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
+void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
+{
+ // return fast if there is nothing to pipeline
+ if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
+ return;
+
+ int i = indexOf(socket);
+
+ // return fast if there was no reply right now processed
+ if (channels[i].reply == 0)
+ return;
+
+ if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.length() >= defaultRePipelineLength)) {
+ return;
+ }
+
+ if (channels[i].pipeliningSupported != QHttpNetworkConnectionChannel::PipeliningProbablySupported)
+ return;
+
+ // the current request that is in must already support pipelining
+ if (!channels[i].request.isPipeliningAllowed())
+ return;
+
+ // the current request must be a idempotent (right now we only check GET)
+ if (channels[i].request.operation() != QHttpNetworkRequest::Get)
+ return;
+
+ // check if socket is connected
+ if (socket->state() != QAbstractSocket::ConnectedState)
+ return;
+
+ // check for resendCurrent
+ if (channels[i].resendCurrent)
+ return;
+
+ // we do not like authentication stuff
+ // ### make sure to be OK with this in later releases
+ if (!channels[i].authenticator.isNull() || !channels[i].authenticator.user().isEmpty())
+ return;
+ if (!channels[i].proxyAuthenticator.isNull() || !channels[i].proxyAuthenticator.user().isEmpty())
+ return;
+
+ // must be in ReadingState or WaitingState
+ if (! (channels[i].state == QHttpNetworkConnectionChannel::WaitingState
+ || channels[i].state == QHttpNetworkConnectionChannel::ReadingState))
+ return;
+
+ int lengthBefore;
+ while (!highPriorityQueue.isEmpty()) {
+ lengthBefore = channels[i].alreadyPipelinedRequests.length();
+ fillPipeline(highPriorityQueue, channels[i]);
+
+ if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) {
+ channels[i].pipelineFlush();
+ return;
+ }
+
+ if (lengthBefore == channels[i].alreadyPipelinedRequests.length())
+ break; // did not process anything, now do the low prio queue
+ }
+
+ while (!lowPriorityQueue.isEmpty()) {
+ lengthBefore = channels[i].alreadyPipelinedRequests.length();
+ fillPipeline(lowPriorityQueue, channels[i]);
+
+ if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) {
+ channels[i].pipelineFlush();
+ return;
+ }
+
+ if (lengthBefore == channels[i].alreadyPipelinedRequests.length())
+ break; // did not process anything
+ }
+
+
+ channels[i].pipelineFlush();
+}
+
+// returns true when the processing of a queue has been done
+bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel)
+{
+ if (queue.isEmpty())
+ return true;
+
+ for (int i = queue.count() - 1; i >= 0; --i) {
+ HttpMessagePair messagePair = queue.at(i);
+ const QHttpNetworkRequest &request = messagePair.first;
+
+ // we currently do not support pipelining if HTTP authentication is used
+ if (!request.url().userInfo().isEmpty())
+ continue;
+
+ // take only GET requests
+ if (request.operation() != QHttpNetworkRequest::Get)
+ continue;
+
+ if (!request.isPipeliningAllowed())
+ continue;
+
+ // remove it from the queue
+ queue.takeAt(i);
+ // we modify the queue we iterate over here, but since we return from the function
+ // afterwards this is fine.
+
+ // actually send it
+ if (!messagePair.second->d_func()->requestIsPrepared)
+ prepareRequest(messagePair);
+ channel.pipelineInto(messagePair);
+
+ // return false because we processed something and need to process again
+ return false;
+ }
+
+ // return true, the queue has been processed and not changed
+ return true;
+}
+
+
+QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket* socket,
+ const QString &extraDetail)
+{
+ 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("QAbstractSocket", "Socket operation timed out"));
+ 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 = extraDetail;
+ break;
+ }
+ return errorString;
+}
+
+// this is called from the destructor of QHttpNetworkReply. It is called when
+// the reply was finished correctly or when it was aborted.
+void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
+{
+ Q_Q(QHttpNetworkConnection);
+
+ // check if the reply is currently being processed or it is pipelined in
+ for (int i = 0; i < channelCount; ++i) {
+ // is the reply associated the currently processing of this channel?
+ if (channels[i].reply == reply) {
+ channels[i].reply = 0;
+ channels[i].request = QHttpNetworkRequest();
+ channels[i].resendCurrent = false;
+
+ if (!reply->isFinished() && !channels[i].alreadyPipelinedRequests.isEmpty()) {
+ // the reply had to be prematurely removed, e.g. it was not finished
+ // therefore we have to requeue the already pipelined requests.
+ channels[i].requeueCurrentlyPipelinedRequests();
+ }
+
+ // if HTTP mandates we should close
+ // or the reply is not finished yet, e.g. it was aborted
+ // we have to close that connection
+ if (reply->d_func()->isConnectionCloseEnabled() || !reply->isFinished())
+ channels[i].close();
+
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ }
+
+ // is the reply inside the pipeline of this channel already?
+ for (int j = 0; j < channels[i].alreadyPipelinedRequests.length(); j++) {
+ if (channels[i].alreadyPipelinedRequests.at(j).second == reply) {
+ // Remove that HttpMessagePair
+ channels[i].alreadyPipelinedRequests.removeAt(j);
+
+ channels[i].requeueCurrentlyPipelinedRequests();
+
+ // Since some requests had already been pipelined, but we removed
+ // one and re-queued the others
+ // we must force a connection close after the request that is
+ // currently in processing has been finished.
+ if (channels[i].reply)
+ channels[i].reply->d_func()->forceConnectionCloseEnabled = true;
+
+ 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;
+ }
+ }
+ }
+}
+
+
+
+// This function must be called from the event loop. The only
+// exception is documented in QHttpNetworkConnectionPrivate::queueRequest
+// although it is called _q_startNextRequest, it will actually start multiple requests when possible
+void QHttpNetworkConnectionPrivate::_q_startNextRequest()
+{
+ // If the QHttpNetworkConnection is currently paused then bail out immediately
+ if (state == PausedState)
+ return;
+
+ //resend the necessary ones.
+ for (int i = 0; i < channelCount; ++i) {
+ if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
+ channels[i].resendCurrent = false;
+ channels[i].state = QHttpNetworkConnectionChannel::IdleState;
+
+ // if this is not possible, error will be emitted and connection terminated
+ if (!channels[i].resetUploadData())
+ continue;
+ channels[i].sendRequest();
+ }
+ }
+
+ // dequeue new ones
+
+ // return fast if there is nothing to do
+ if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
+ return;
+ // try to get a free AND connected socket
+ for (int i = 0; i < channelCount; ++i) {
+ if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
+ if (dequeueRequest(channels[i].socket))
+ channels[i].sendRequest();
+ }
+ }
+
+ // try to push more into all sockets
+ // ### FIXME we should move this to the beginning of the function
+ // as soon as QtWebkit is properly using the pipelining
+ // (e.g. not for XMLHttpRequest or the first page load)
+ // ### FIXME we should also divide the requests more even
+ // on the connected sockets
+ //tryToFillPipeline(socket);
+ // return fast if there is nothing to pipeline
+ if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
+ return;
+ for (int i = 0; i < channelCount; i++)
+ if (channels[i].socket->state() == QAbstractSocket::ConnectedState)
+ fillPipeline(channels[i].socket);
+
+ // If there is not already any connected channels we need to connect a new one.
+ // We do not pair the channel with the request until we know if it is
+ // connected or not. This is to reuse connected channels before we connect new once.
+ int queuedRequest = highPriorityQueue.count() + lowPriorityQueue.count();
+ for (int i = 0; i < channelCount; ++i) {
+ if (channels[i].socket->state() == QAbstractSocket::ConnectingState)
+ queuedRequest--;
+ if ( queuedRequest <=0 )
+ break;
+ if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) {
+ channels[i].ensureConnection();
+ queuedRequest--;
+ }
+ }
+}
+
+
+void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply)
+{
+ for (int i = 0 ; i < channelCount; ++i) {
+ if (channels[i].reply == reply) {
+ // emulate a readyRead() from the socket
+ QMetaObject::invokeMethod(&channels[i], "_q_readyRead", Qt::QueuedConnection);
+ return;
+ }
+ }
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession)
+ : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
+{
+ Q_D(QHttpNetworkConnection);
+ d->networkSession = networkSession;
+ d->init();
+}
+
+QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession)
+ : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent)
+{
+ Q_D(QHttpNetworkConnection);
+ d->networkSession = networkSession;
+ d->init();
+}
+#else
+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(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent)
+ : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent)
+{
+ Q_D(QHttpNetworkConnection);
+ d->init();
+}
+#endif
+
+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);
+}
+
+bool QHttpNetworkConnection::isSsl() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->encrypt;
+}
+
+QHttpNetworkConnectionChannel *QHttpNetworkConnection::channels() const
+{
+ return d_func()->channels;
+}
+
+#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
+
+
+// SSL support below
+#ifndef QT_NO_OPENSSL
+void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config)
+{
+ Q_D(QHttpNetworkConnection);
+ if (!d->encrypt)
+ return;
+
+ // 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 (!d->encrypt)
+ return;
+
+ 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].ignoreAllSslErrors = true;
+ }
+
+ } else {
+ static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors();
+ d->channels[channel].ignoreAllSslErrors = true;
+ }
+}
+
+void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int channel)
+{
+ Q_D(QHttpNetworkConnection);
+ if (!d->encrypt)
+ return;
+
+ if (channel == -1) { // ignore for all channels
+ for (int i = 0; i < d->channelCount; ++i) {
+ static_cast<QSslSocket *>(d->channels[i].socket)->ignoreSslErrors(errors);
+ d->channels[i].ignoreSslErrorsList = errors;
+ }
+
+ } else {
+ static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors(errors);
+ d->channels[channel].ignoreSslErrorsList = errors;
+ }
+}
+
+#endif //QT_NO_OPENSSL
+
+#ifndef QT_NO_NETWORKPROXY
+// only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not
+// from QHttpNetworkConnectionChannel::handleAuthenticationChallenge
+// e.g. it is for SOCKS proxies which require authentication.
+void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth)
+{
+ // Also pause the connection because socket notifiers may fire while an user
+ // dialog is displaying
+ pauseConnection();
+ emit chan->reply->proxyAuthenticationRequired(proxy, auth);
+ resumeConnection();
+ int i = indexOf(chan->socket);
+ copyCredentials(i, auth, true);
+}
+#endif
+
+
+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 0000000000..adb779f473
--- /dev/null
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -0,0 +1,230 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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>
+#include <QtNetwork/qnetworksession.h>
+
+#include <private/qobject_p.h>
+#include <qauthenticator.h>
+#include <qnetworkproxy.h>
+#include <qbuffer.h>
+
+#include <private/qhttpnetworkheader_p.h>
+#include <private/qhttpnetworkrequest_p.h>
+#include <private/qhttpnetworkreply_p.h>
+
+#include <private/qhttpnetworkconnectionchannel_p.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 QByteArray;
+
+class QHttpNetworkConnectionPrivate;
+class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject
+{
+ Q_OBJECT
+public:
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>());
+ QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>());
+#else
+ QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0);
+ QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0);
+#endif
+ ~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
+
+ bool isSsl() const;
+
+ QHttpNetworkConnectionChannel *channels() const;
+
+#ifndef QT_NO_OPENSSL
+ void setSslConfiguration(const QSslConfiguration &config);
+ void ignoreSslErrors(int channel = -1);
+ void ignoreSslErrors(const QList<QSslError> &errors, int channel = -1);
+#endif
+
+private:
+ Q_DECLARE_PRIVATE(QHttpNetworkConnection)
+ Q_DISABLE_COPY(QHttpNetworkConnection)
+ friend class QHttpNetworkReply;
+ friend class QHttpNetworkReplyPrivate;
+ friend class QHttpNetworkConnectionChannel;
+
+ Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
+};
+
+
+// private classes
+typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
+
+
+class QHttpNetworkConnectionPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QHttpNetworkConnection)
+public:
+ static const int defaultChannelCount;
+ static const int defaultPipelineLength;
+ static const int defaultRePipelineLength;
+
+ enum ConnectionState {
+ RunningState = 0,
+ PausedState = 1,
+ };
+
+ QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt);
+ QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt);
+ ~QHttpNetworkConnectionPrivate();
+ void init();
+
+ void pauseConnection();
+ void resumeConnection();
+ ConnectionState state;
+
+ enum { ChunkSize = 4096 };
+
+ int indexOf(QAbstractSocket *socket) const;
+
+ QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request);
+ void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke
+ bool dequeueRequest(QAbstractSocket *socket);
+ void prepareRequest(HttpMessagePair &request);
+
+ void fillPipeline(QAbstractSocket *socket);
+ bool fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel);
+
+ // read more HTTP body after the next event loop spin
+ void readMoreLater(QHttpNetworkReply *reply);
+
+ void copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy);
+
+ // private slots
+ void _q_startNextRequest(); // send the next request from the queue
+
+ void createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request);
+
+ QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket,
+ const QString &extraDetail = QString());
+
+#ifndef QT_NO_COMPRESS
+ bool expand(QAbstractSocket *socket, QHttpNetworkReply *reply, bool dataComplete);
+#endif
+ void removeReply(QHttpNetworkReply *reply);
+
+ QString hostName;
+ quint16 port;
+ bool encrypt;
+
+ const int channelCount;
+ QHttpNetworkConnectionChannel *channels; // parallel connections to the server
+
+ qint64 uncompressedBytesAvailable(const QHttpNetworkReply &reply) const;
+ qint64 uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const;
+
+
+ void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
+ bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
+
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy networkProxy;
+ void emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth);
+#endif
+
+ //The request queues
+ QList<HttpMessagePair> highPriorityQueue;
+ QList<HttpMessagePair> lowPriorityQueue;
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ QSharedPointer<QNetworkSession> networkSession;
+#endif
+
+ friend class QHttpNetworkConnectionChannel;
+};
+
+
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP
+
+#endif
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
new file mode 100644
index 0000000000..6fbc6f8056
--- /dev/null
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -0,0 +1,1162 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpnetworkconnection_p.h"
+#include "qhttpnetworkconnectionchannel_p.h"
+#include "private/qnoncontiguousbytedevice_p.h"
+
+#include <qpair.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_BEARERMANAGEMENT
+#include "private/qnetworksession_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
+
+QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
+ : socket(0)
+ , ssl(false)
+ , state(IdleState)
+ , reply(0)
+ , written(0)
+ , bytesTotal(0)
+ , resendCurrent(false)
+ , lastStatus(0)
+ , pendingEncrypt(false)
+ , reconnectAttempts(2)
+ , authMethod(QAuthenticatorPrivate::None)
+ , proxyAuthMethod(QAuthenticatorPrivate::None)
+#ifndef QT_NO_OPENSSL
+ , ignoreAllSslErrors(false)
+#endif
+ , pipeliningSupported(PipeliningSupportUnknown)
+ , connection(0)
+{
+ // Inlining this function in the header leads to compiler error on
+ // release-armv5, on at least timebox 9.2 and 10.1.
+}
+
+void QHttpNetworkConnectionChannel::init()
+{
+#ifndef QT_NO_OPENSSL
+ if (connection->d_func()->encrypt)
+ socket = new QSslSocket;
+ else
+ socket = new QTcpSocket;
+#else
+ socket = new QTcpSocket;
+#endif
+#ifndef QT_NO_BEARERMANAGEMENT
+ //push session down to socket
+ if (networkSession)
+ socket->setProperty("_q_networksession", QVariant::fromValue(networkSession));
+#endif
+#ifndef QT_NO_NETWORKPROXY
+ // Set by QNAM anyway, but let's be safe here
+ socket->setProxy(QNetworkProxy::NoProxy);
+#endif
+
+ QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(_q_bytesWritten(qint64)),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(connected()),
+ this, SLOT(_q_connected()),
+ Qt::DirectConnection);
+ QObject::connect(socket, SIGNAL(readyRead()),
+ this, SLOT(_q_readyRead()),
+ Qt::DirectConnection);
+
+ // The disconnected() and error() signals may already come
+ // while calling connectToHost().
+ // In case of a cached hostname or an IP this
+ // will then emit a signal to the user of QNetworkReply
+ // but cannot be caught because the user did not have a chance yet
+ // to connect to QNetworkReply's signals.
+ qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
+ QObject::connect(socket, SIGNAL(disconnected()),
+ this, SLOT(_q_disconnected()),
+ Qt::QueuedConnection);
+ QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(_q_error(QAbstractSocket::SocketError)),
+ Qt::QueuedConnection);
+
+
+#ifndef QT_NO_NETWORKPROXY
+ QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ Qt::DirectConnection);
+#endif
+
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ if (sslSocket) {
+ // won't be a sslSocket if encrypt is false
+ QObject::connect(sslSocket, SIGNAL(encrypted()),
+ this, SLOT(_q_encrypted()),
+ Qt::DirectConnection);
+ QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
+ this, SLOT(_q_sslErrors(QList<QSslError>)),
+ Qt::DirectConnection);
+ QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
+ this, SLOT(_q_encryptedBytesWritten(qint64)),
+ Qt::DirectConnection);
+ }
+#endif
+}
+
+
+void QHttpNetworkConnectionChannel::close()
+{
+ if (socket->state() == QAbstractSocket::UnconnectedState)
+ state = QHttpNetworkConnectionChannel::IdleState;
+ else
+ state = QHttpNetworkConnectionChannel::ClosingState;
+
+ socket->close();
+}
+
+
+bool QHttpNetworkConnectionChannel::sendRequest()
+{
+ if (!reply) {
+ // heh, how should that happen!
+ qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply";
+ state = QHttpNetworkConnectionChannel::IdleState;
+ return false;
+ }
+
+ switch (state) {
+ case QHttpNetworkConnectionChannel::IdleState: { // write the header
+ if (!ensureConnection()) {
+ // wait for the connection (and encryption) to be done
+ // sendRequest will be called again from either
+ // _q_connected or _q_encrypted
+ return false;
+ }
+ written = 0; // excluding the header
+ bytesTotal = 0;
+
+ QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
+ replyPrivate->clear();
+ replyPrivate->connection = connection;
+ replyPrivate->connectionChannel = this;
+ replyPrivate->autoDecompress = request.d->autoDecompress;
+ replyPrivate->pipeliningUsed = false;
+
+ // if the url contains authentication parameters, use the new ones
+ // both channels will use the new authentication parameters
+ if (!request.url().userInfo().isEmpty() && request.withCredentials()) {
+ QUrl url = request.url();
+ QAuthenticator &auth = authenticator;
+ if (url.userName() != auth.user()
+ || (!url.password().isEmpty() && url.password() != auth.password())) {
+ auth.setUser(url.userName());
+ auth.setPassword(url.password());
+ emit reply->cacheCredentials(request, &auth);
+ connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &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());
+ request.setUrl(url);
+ }
+ // Will only be false if QtWebKit is performing a cross-origin XMLHttpRequest
+ // and withCredentials has not been set to true.
+ if (request.withCredentials())
+ connection->d_func()->createAuthorization(socket, request);
+#ifndef QT_NO_NETWORKPROXY
+ QByteArray header = QHttpNetworkRequestPrivate::header(request,
+ (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
+#else
+ QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
+#endif
+ socket->write(header);
+ // flushing is dangerous (QSslSocket calls transmit which might read or error)
+// socket->flush();
+ QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
+ if (uploadByteDevice) {
+ // connect the signals so this function gets called again
+ QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead()));
+
+ bytesTotal = request.contentLength();
+
+ state = QHttpNetworkConnectionChannel::WritingState; // start writing data
+ sendRequest(); //recurse
+ } else {
+ state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
+ sendRequest(); //recurse
+ }
+
+ break;
+ }
+ case QHttpNetworkConnectionChannel::WritingState:
+ {
+ // write the data
+ QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
+ if (!uploadByteDevice || bytesTotal == written) {
+ if (uploadByteDevice)
+ emit reply->dataSendProgress(written, bytesTotal);
+ state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
+ sendRequest(); // recurse
+ break;
+ }
+
+ // only feed the QTcpSocket buffer when there is less than 32 kB in it
+ const qint64 socketBufferFill = 32*1024;
+ const qint64 socketWriteMaxSize = 16*1024;
+
+
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ // if it is really an ssl socket, check more than just bytesToWrite()
+ while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0))
+ <= socketBufferFill && bytesTotal != written)
+#else
+ while (socket->bytesToWrite() <= socketBufferFill
+ && bytesTotal != written)
+#endif
+ {
+ // get pointer to upload data
+ qint64 currentReadSize = 0;
+ qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
+ const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
+
+ if (currentReadSize == -1) {
+ // premature eof happened
+ connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
+ return false;
+ break;
+ } else if (readPointer == 0 || currentReadSize == 0) {
+ // nothing to read currently, break the loop
+ break;
+ } else {
+ qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
+ if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
+ // socket broke down
+ connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
+ return false;
+ } else {
+ written += currentWriteSize;
+ uploadByteDevice->advanceReadPointer(currentWriteSize);
+
+ emit reply->dataSendProgress(written, bytesTotal);
+
+ if (written == bytesTotal) {
+ // make sure this function is called once again
+ state = QHttpNetworkConnectionChannel::WaitingState;
+ sendRequest();
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case QHttpNetworkConnectionChannel::WaitingState:
+ {
+ QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
+ if (uploadByteDevice) {
+ QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()));
+ }
+
+ // HTTP pipelining
+ //connection->d_func()->fillPipeline(socket);
+ //socket->flush();
+
+ // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
+ // this is needed if the sends an reply before we have finished sending the request. In that
+ // case receiveReply had been called before but ignored the server reply
+ if (socket->bytesAvailable())
+ QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
+ break;
+ }
+ case QHttpNetworkConnectionChannel::ReadingState:
+ // ignore _q_bytesWritten in these states
+ // fall through
+ default:
+ break;
+ }
+ return true;
+}
+
+
+void QHttpNetworkConnectionChannel::_q_receiveReply()
+{
+ Q_ASSERT(socket);
+
+ if (!reply) {
+ // heh, how should that happen!
+ qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply,"
+ << socket->bytesAvailable() << "bytes on socket.";
+ close();
+ return;
+ }
+
+ // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
+ // this function is called from _q_disconnected which is called because
+ // of ~QHttpNetworkConnectionPrivate
+ if (!qobject_cast<QHttpNetworkConnection*>(connection)) {
+ return;
+ }
+
+ QAbstractSocket::SocketState socketState = socket->state();
+
+ // connection might be closed to signal the end of data
+ if (socketState == QAbstractSocket::UnconnectedState) {
+ if (socket->bytesAvailable() <= 0) {
+ if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
+ // finish this reply. this case happens when the server did not send a content length
+ reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
+ allDone();
+ return;
+ } else {
+ handleUnexpectedEOF();
+ return;
+ }
+ } else {
+ // socket not connected but still bytes for reading.. just continue in this function
+ }
+ }
+
+ // read loop for the response
+ qint64 bytes = 0;
+ qint64 lastBytes = bytes;
+ do {
+ lastBytes = bytes;
+
+ QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state;
+ switch (state) {
+ case QHttpNetworkReplyPrivate::NothingDoneState: {
+ state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
+ // fallthrough
+ }
+ case QHttpNetworkReplyPrivate::ReadingStatusState: {
+ qint64 statusBytes = reply->d_func()->readStatus(socket);
+ if (statusBytes == -1) {
+ // connection broke while reading status. also handled if later _q_disconnected is called
+ handleUnexpectedEOF();
+ return;
+ }
+ bytes += statusBytes;
+ lastStatus = reply->d_func()->statusCode;
+ break;
+ }
+ case QHttpNetworkReplyPrivate::ReadingHeaderState: {
+ QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
+ qint64 headerBytes = replyPrivate->readHeader(socket);
+ if (headerBytes == -1) {
+ // connection broke while reading headers. also handled if later _q_disconnected is called
+ handleUnexpectedEOF();
+ return;
+ }
+ bytes += headerBytes;
+ // If headers were parsed successfully now it is the ReadingDataState
+ if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) {
+ if (replyPrivate->isGzipped() && replyPrivate->autoDecompress) {
+ // remove the Content-Length from header
+ replyPrivate->removeAutoDecompressHeader();
+ } else {
+ replyPrivate->autoDecompress = false;
+ }
+ if (replyPrivate->statusCode == 100) {
+ replyPrivate->clearHttpLayerInformation();
+ replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
+ break; // ignore
+ }
+ if (replyPrivate->shouldEmitSignals())
+ emit reply->headerChanged();
+ // After headerChanged had been emitted
+ // we can suddenly have a replyPrivate->userProvidedDownloadBuffer
+ // this is handled in the ReadingDataState however
+
+ if (!replyPrivate->expectContent()) {
+ replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState;
+ allDone();
+ break;
+ }
+ }
+ break;
+ }
+ case QHttpNetworkReplyPrivate::ReadingDataState: {
+ QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
+ if (socket->state() == QAbstractSocket::ConnectedState &&
+ replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
+ // (only do the following when still connected, not when we have already been disconnected and there is still data)
+ // We already have some HTTP body data. We don't read more from the socket until
+ // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more,
+ // we could not limit our read buffer usage.
+ // We only do this when shouldEmitSignals==true because our HTTP parsing
+ // always needs to parse the 401/407 replies. Therefore they don't really obey
+ // to the read buffer maximum size, but we don't care since they should be small.
+ return;
+ }
+
+ if (replyPrivate->userProvidedDownloadBuffer) {
+ // the user provided a direct buffer where we should put all our data in.
+ // this only works when we can tell the user the content length and he/she can allocate
+ // the buffer in that size.
+ // note that this call will read only from the still buffered data
+ qint64 haveRead = replyPrivate->readBodyVeryFast(socket, replyPrivate->userProvidedDownloadBuffer + replyPrivate->totalProgress);
+ bytes += haveRead;
+ replyPrivate->totalProgress += haveRead;
+
+ // the user will get notified of it via progress signal
+ if (haveRead > 0)
+ emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
+ } else if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress
+ && replyPrivate->bodyLength > 0) {
+ // bulk files like images should fulfill these properties and
+ // we can therefore save on memory copying
+ qint64 haveRead = replyPrivate->readBodyFast(socket, &replyPrivate->responseData);
+ bytes += haveRead;
+ replyPrivate->totalProgress += haveRead;
+ if (replyPrivate->shouldEmitSignals()) {
+ emit reply->readyRead();
+ emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
+ }
+ }
+ else
+ {
+ // use the traditional slower reading (for compressed encoding, chunked encoding,
+ // no content-length etc)
+ QByteDataBuffer byteDatas;
+ qint64 haveRead = replyPrivate->readBody(socket, &byteDatas);
+ if (haveRead) {
+ bytes += haveRead;
+ if (replyPrivate->autoDecompress)
+ replyPrivate->appendCompressedReplyData(byteDatas);
+ else
+ replyPrivate->appendUncompressedReplyData(byteDatas);
+
+ if (!replyPrivate->autoDecompress) {
+ replyPrivate->totalProgress += bytes;
+ if (replyPrivate->shouldEmitSignals()) {
+ // important: At the point of this readyRead(), the byteDatas list must be empty,
+ // else implicit sharing will trigger memcpy when the user is reading data!
+ emit reply->readyRead();
+ emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
+ }
+ }
+#ifndef QT_NO_COMPRESS
+ else if (!expand(false)) { // expand a chunk if possible
+ // If expand() failed we can just return, it had already called connection->emitReplyError()
+ return;
+ }
+#endif
+ }
+ }
+ // still in ReadingDataState? This function will be called again by the socket's readyRead
+ if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState)
+ break;
+
+ // everything done, fall through
+ }
+ case QHttpNetworkReplyPrivate::AllDoneState:
+ allDone();
+ break;
+ default:
+ break;
+ }
+ } while (bytes != lastBytes && reply);
+}
+
+// called when unexpectedly reading a -1 or when data is expected but socket is closed
+void QHttpNetworkConnectionChannel::handleUnexpectedEOF()
+{
+ Q_ASSERT(reply);
+ if (reconnectAttempts <= 0) {
+ // too many errors reading/receiving/parsing the status, close the socket and emit error
+ requeueCurrentlyPipelinedRequests();
+ close();
+ reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket);
+ emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString);
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ } else {
+ reconnectAttempts--;
+ reply->d_func()->clear();
+ reply->d_func()->connection = connection;
+ reply->d_func()->connectionChannel = this;
+ closeAndResendCurrentRequest();
+ }
+}
+
+bool QHttpNetworkConnectionChannel::ensureConnection()
+{
+ QAbstractSocket::SocketState socketState = socket->state();
+
+ // resend this request after we receive the disconnected signal
+ if (socketState == QAbstractSocket::ClosingState) {
+ if (reply)
+ resendCurrent = true;
+ return false;
+ }
+
+ // already trying to connect?
+ if (socketState == QAbstractSocket::HostLookupState ||
+ socketState == QAbstractSocket::ConnectingState) {
+ return false;
+ }
+
+ // make sure that this socket is in a connected state, if not initiate
+ // connection to the host.
+ if (socketState != QAbstractSocket::ConnectedState) {
+ // connect to the host if not already connected.
+ state = QHttpNetworkConnectionChannel::ConnectingState;
+ pendingEncrypt = ssl;
+
+ // reset state
+ pipeliningSupported = PipeliningSupportUnknown;
+
+ // 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(authenticator);
+ if (priv && priv->phase == QAuthenticatorPrivate::Done)
+ priv->phase = QAuthenticatorPrivate::Start;
+ priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator);
+ if (priv && priv->phase == QAuthenticatorPrivate::Done)
+ priv->phase = QAuthenticatorPrivate::Start;
+
+ QString connectHost = connection->d_func()->hostName;
+ qint16 connectPort = connection->d_func()->port;
+
+#ifndef QT_NO_NETWORKPROXY
+ // HTTPS always use transparent proxy.
+ if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) {
+ connectHost = connection->d_func()->networkProxy.hostName();
+ connectPort = connection->d_func()->networkProxy.port();
+ }
+#endif
+ if (ssl) {
+#ifndef QT_NO_OPENSSL
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
+ sslSocket->connectToHostEncrypted(connectHost, connectPort);
+ if (ignoreAllSslErrors)
+ sslSocket->ignoreSslErrors();
+ sslSocket->ignoreSslErrors(ignoreSslErrorsList);
+
+ // limit the socket read buffer size. we will read everything into
+ // the QHttpNetworkReply anyway, so let's grow only that and not
+ // here and there.
+ socket->setReadBufferSize(64*1024);
+#else
+ connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
+#endif
+ } else {
+ // In case of no proxy we can use the Unbuffered QTcpSocket
+#ifndef QT_NO_NETWORKPROXY
+ if (connection->d_func()->networkProxy.type() == QNetworkProxy::NoProxy
+ && connection->cacheProxy().type() == QNetworkProxy::NoProxy
+ && connection->transparentProxy().type() == QNetworkProxy::NoProxy) {
+#endif
+ socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered);
+ // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
+ socket->setReadBufferSize(1*1024);
+#ifndef QT_NO_NETWORKPROXY
+ } else {
+ socket->connectToHost(connectHost, connectPort);
+
+ // limit the socket read buffer size. we will read everything into
+ // the QHttpNetworkReply anyway, so let's grow only that and not
+ // here and there.
+ socket->setReadBufferSize(64*1024);
+ }
+#endif
+ }
+ return false;
+ }
+ return true;
+}
+
+
+#ifndef QT_NO_COMPRESS
+bool QHttpNetworkConnectionChannel::expand(bool dataComplete)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ qint64 total = reply->d_func()->compressedData.size();
+ if (total >= CHUNK || dataComplete) {
+ // uncompress the data
+ QByteArray content, inflated;
+ content = reply->d_func()->compressedData;
+ reply->d_func()->compressedData.clear();
+
+ 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();
+ reply->d_func()->appendUncompressedReplyData(inflated);
+ if (reply->d_func()->shouldEmitSignals()) {
+ // important: At the point of this readyRead(), inflated must be cleared,
+ // else implicit sharing will trigger memcpy when the user is reading data!
+ emit reply->readyRead();
+ emit reply->dataReadProgress(reply->d_func()->totalProgress, 0);
+ }
+ }
+ } else {
+ connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+
+void QHttpNetworkConnectionChannel::allDone()
+{
+ Q_ASSERT(reply);
+#ifndef QT_NO_COMPRESS
+ // expand the whole data.
+ if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd) {
+ bool expandResult = expand(true);
+ // If expand() failed we can just return, it had already called connection->emitReplyError()
+ if (!expandResult)
+ return;
+ }
+#endif
+
+ if (!reply) {
+ qWarning() << "QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt.nokia.com/";
+ return;
+ }
+
+ // while handling 401 & 407, we might reset the status code, so save this.
+ bool emitFinished = reply->d_func()->shouldEmitSignals();
+ bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
+ detectPipeliningSupport();
+
+ handleStatus();
+ // handleStatus() might have removed the reply because it already called connection->emitReplyError()
+
+ // 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 (reply && 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.
+ reconnectAttempts = 2;
+
+ // now the channel can be seen as free/idle again, all signal emissions for the reply have been done
+ if (state != QHttpNetworkConnectionChannel::ClosingState)
+ state = QHttpNetworkConnectionChannel::IdleState;
+
+ // if it does not need to be sent again we can set it to 0
+ // the previous code did not do that and we had problems with accidental re-sending of a
+ // finished request.
+ // Note that this may trigger a segfault at some other point. But then we can fix the underlying
+ // problem.
+ if (!resendCurrent) {
+ request = QHttpNetworkRequest();
+ reply = 0;
+ }
+
+ // move next from pipeline to current request
+ if (!alreadyPipelinedRequests.isEmpty()) {
+ if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
+ // move the pipelined ones back to the main queue
+ requeueCurrentlyPipelinedRequests();
+ close();
+ } else {
+ // there were requests pipelined in and we can continue
+ HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst();
+
+ request = messagePair.first;
+ reply = messagePair.second;
+ state = QHttpNetworkConnectionChannel::ReadingState;
+ resendCurrent = false;
+
+ written = 0; // message body, excluding the header, irrelevant here
+ bytesTotal = 0; // message body total, excluding the header, irrelevant here
+
+ // pipeline even more
+ connection->d_func()->fillPipeline(socket);
+
+ // continue reading
+ //_q_receiveReply();
+ // this was wrong, allDone gets called from that function anyway.
+ }
+ } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
+ // this is weird. we had nothing pipelined but still bytes available. better close it.
+ //if (socket->bytesAvailable() > 0)
+ // close();
+ //
+ // FIXME
+ // We do not close it anymore now, but should introduce this again after having fixed
+ // the chunked decoder in QHttpNetworkReply to read the whitespace after the last chunk.
+ // (Currently this is worked around by readStatus in the QHttpNetworkReply ignoring
+ // leading whitespace.
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ } else if (alreadyPipelinedRequests.isEmpty()) {
+ if (connectionCloseEnabled)
+ if (socket->state() != QAbstractSocket::UnconnectedState)
+ close();
+ if (qobject_cast<QHttpNetworkConnection*>(connection))
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+}
+
+void QHttpNetworkConnectionChannel::detectPipeliningSupport()
+{
+ Q_ASSERT(reply);
+ // detect HTTP Pipelining support
+ QByteArray serverHeaderField;
+ if (
+ // check for HTTP/1.1
+ (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
+ // check for not having connection close
+ && (!reply->d_func()->isConnectionCloseEnabled())
+ // check if it is still connected
+ && (socket->state() == QAbstractSocket::ConnectedState)
+ // check for broken servers in server reply header
+ // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
+ && (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
+ && (!serverHeaderField.contains("Microsoft-IIS/5."))
+ && (!serverHeaderField.contains("Netscape-Enterprise/3."))
+ // this is adpoted from the knowledge of the Nokia 7.x browser team (DEF143319)
+ && (!serverHeaderField.contains("WebLogic"))
+ ) {
+ pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
+ } else {
+ pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
+ }
+}
+
+// called when the connection broke and we need to queue some pipelined requests again
+void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests()
+{
+ for (int i = 0; i < alreadyPipelinedRequests.length(); i++)
+ connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
+ alreadyPipelinedRequests.clear();
+
+ // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
+ // this function is called from _q_disconnected which is called because
+ // of ~QHttpNetworkConnectionPrivate
+ if (qobject_cast<QHttpNetworkConnection*>(connection))
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+}
+
+void QHttpNetworkConnectionChannel::handleStatus()
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(reply);
+
+ int statusCode = reply->statusCode();
+ bool resend = false;
+
+ switch (statusCode) {
+ case 401: // auth required
+ case 407: // proxy auth required
+ if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
+ if (resend) {
+ if (!resetUploadData())
+ break;
+
+ reply->d_func()->eraseData();
+
+ if (alreadyPipelinedRequests.isEmpty()) {
+ // this does a re-send without closing the connection
+ resendCurrent = true;
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ } else {
+ // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
+ closeAndResendCurrentRequest();
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+ }
+ } else {
+ emit reply->headerChanged();
+ emit reply->readyRead();
+ QNetworkReply::NetworkError errorCode = (statusCode == 407)
+ ? QNetworkReply::ProxyAuthenticationRequiredError
+ : QNetworkReply::AuthenticationRequiredError;
+ reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
+ emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
+ }
+ break;
+ default:
+ if (qobject_cast<QHttpNetworkConnection*>(connection))
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+}
+
+bool QHttpNetworkConnectionChannel::resetUploadData()
+{
+ if (!reply) {
+ //this happens if server closes connection while QHttpNetworkConnectionPrivate::_q_startNextRequest is pending
+ return false;
+ }
+ QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
+ if (!uploadByteDevice)
+ return true;
+
+ if (uploadByteDevice->reset()) {
+ written = 0;
+ return true;
+ } else {
+ connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
+ return false;
+ }
+}
+
+
+void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair)
+{
+ // this is only called for simple GET
+
+ QHttpNetworkRequest &request = pair.first;
+ QHttpNetworkReply *reply = pair.second;
+ reply->d_func()->clear();
+ reply->d_func()->connection = connection;
+ reply->d_func()->connectionChannel = this;
+ reply->d_func()->autoDecompress = request.d->autoDecompress;
+ reply->d_func()->pipeliningUsed = true;
+
+#ifndef QT_NO_NETWORKPROXY
+ pipeline.append(QHttpNetworkRequestPrivate::header(request,
+ (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)));
+#else
+ pipeline.append(QHttpNetworkRequestPrivate::header(request, false));
+#endif
+
+ alreadyPipelinedRequests.append(pair);
+
+ // pipelineFlush() needs to be called at some point afterwards
+}
+
+void QHttpNetworkConnectionChannel::pipelineFlush()
+{
+ if (pipeline.isEmpty())
+ return;
+
+ // The goal of this is so that we have everything in one TCP packet.
+ // For the Unbuffered QTcpSocket this is manually needed, the buffered
+ // QTcpSocket does it automatically.
+ // Also, sometimes the OS does it for us (Nagle's algorithm) but that
+ // happens only sometimes.
+ socket->write(pipeline);
+ pipeline.clear();
+}
+
+
+void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
+{
+ requeueCurrentlyPipelinedRequests();
+ close();
+ if (reply)
+ resendCurrent = true;
+ if (qobject_cast<QHttpNetworkConnection*>(connection))
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+}
+
+bool QHttpNetworkConnectionChannel::isSocketBusy() const
+{
+ return (state & QHttpNetworkConnectionChannel::BusyState);
+}
+
+bool QHttpNetworkConnectionChannel::isSocketWriting() const
+{
+ return (state & QHttpNetworkConnectionChannel::WritingState);
+}
+
+bool QHttpNetworkConnectionChannel::isSocketWaiting() const
+{
+ return (state & QHttpNetworkConnectionChannel::WaitingState);
+}
+
+bool QHttpNetworkConnectionChannel::isSocketReading() const
+{
+ return (state & QHttpNetworkConnectionChannel::ReadingState);
+}
+
+//private slots
+void QHttpNetworkConnectionChannel::_q_readyRead()
+{
+ if (socket->state() == QAbstractSocket::ConnectedState && socket->bytesAvailable() == 0) {
+ // We got a readyRead but no bytes are available..
+ // This happens for the Unbuffered QTcpSocket
+ // Also check if socket is in ConnectedState since
+ // this function may also be invoked via the event loop.
+ char c;
+ qint64 ret = socket->peek(&c, 1);
+ if (ret < 0) {
+ _q_error(socket->error());
+ // We still need to handle the reply so it emits its signals etc.
+ if (reply)
+ _q_receiveReply();
+ return;
+ }
+ }
+
+ if (isSocketWaiting() || isSocketReading()) {
+ state = QHttpNetworkConnectionChannel::ReadingState;
+ if (reply)
+ _q_receiveReply();
+ }
+}
+
+void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
+{
+ Q_UNUSED(bytes);
+ // bytes have been written to the socket. write even more of them :)
+ if (isSocketWriting())
+ sendRequest();
+ // otherwise we do nothing
+}
+
+void QHttpNetworkConnectionChannel::_q_disconnected()
+{
+ if (state == QHttpNetworkConnectionChannel::ClosingState) {
+ state = QHttpNetworkConnectionChannel::IdleState;
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ }
+
+ // read the available data before closing
+ if (isSocketWaiting() || isSocketReading()) {
+ if (reply) {
+ state = QHttpNetworkConnectionChannel::ReadingState;
+ _q_receiveReply();
+ }
+ } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
+ // re-sending request because the socket was in ClosingState
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+ state = QHttpNetworkConnectionChannel::IdleState;
+
+ requeueCurrentlyPipelinedRequests();
+ close();
+}
+
+
+void QHttpNetworkConnectionChannel::_q_connected()
+{
+ // improve performance since we get the request sent by the kernel ASAP
+ //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+ // We have this commented out now. It did not have the effect we wanted. If we want to
+ // do this properly, Qt has to combine multiple HTTP requests into one buffer
+ // and send this to the kernel in one syscall and then the kernel immediately sends
+ // it as one TCP packet because of TCP_NODELAY.
+ // However, this code is currently not in Qt, so we rely on the kernel combining
+ // the requests into one TCP packet.
+
+ // not sure yet if it helps, but it makes sense
+ socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
+
+ pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
+
+ // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
+ //channels[i].reconnectAttempts = 2;
+ if (!pendingEncrypt) {
+ state = QHttpNetworkConnectionChannel::IdleState;
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply)
+ sendRequest();
+ }
+}
+
+
+void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
+{
+ if (!socket)
+ return;
+ 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 (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
+ if (reconnectAttempts-- > 0) {
+ closeAndResendCurrentRequest();
+ return;
+ } else {
+ errorCode = QNetworkReply::RemoteHostClosedError;
+ }
+ } else if (state == QHttpNetworkConnectionChannel::ReadingState) {
+ if (!reply->d_func()->expectContent()) {
+ // No content expected, this is a valid way to have the connection closed by the server
+ return;
+ }
+ if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
+ // There was no content-length header and it's not chunked encoding,
+ // so this is a valid way to have the connection closed by the server
+ return;
+ }
+ // ok, we got a disconnect even though we did not expect it
+ errorCode = QNetworkReply::RemoteHostClosedError;
+ } else {
+ errorCode = QNetworkReply::RemoteHostClosedError;
+ }
+ break;
+ case QAbstractSocket::SocketTimeoutError:
+ // try to reconnect/resend before sending an error.
+ if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
+ closeAndResendCurrentRequest();
+ return;
+ }
+ 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<QHttpNetworkConnection> that = connection;
+ QString errorString = connection->d_func()->errorDetail(errorCode, socket, socket->errorString());
+
+ // Need to dequeu the request so that we can emit the error.
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply) {
+ reply->d_func()->errorString = errorString;
+ emit reply->finishedWithError(errorCode, errorString);
+ reply = 0;
+ }
+ // send the next request
+ QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
+
+ if (that) //signal emission triggered event loop
+ close();
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
+{
+ // Need to dequeue the request before we can emit the error.
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply)
+ connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
+}
+#endif
+
+void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
+{
+ sendRequest();
+}
+
+#ifndef QT_NO_OPENSSL
+void QHttpNetworkConnectionChannel::_q_encrypted()
+{
+ if (!socket)
+ return; // ### error
+ state = QHttpNetworkConnectionChannel::IdleState;
+ pendingEncrypt = false;
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply)
+ sendRequest();
+}
+
+void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
+{
+ if (!socket)
+ return;
+ //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
+ // Also pause the connection because socket notifiers may fire while an user
+ // dialog is displaying
+ connection->d_func()->pauseConnection();
+ if (pendingEncrypt && !reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply)
+ emit reply->sslErrors(errors);
+ connection->d_func()->resumeConnection();
+}
+
+void QHttpNetworkConnectionChannel::_q_encryptedBytesWritten(qint64 bytes)
+{
+ Q_UNUSED(bytes);
+ // bytes have been written to the socket. write even more of them :)
+ if (isSocketWriting())
+ sendRequest();
+ // otherwise we do nothing
+}
+
+#endif
+
+void QHttpNetworkConnectionChannel::setConnection(QHttpNetworkConnection *c)
+{
+ // Inlining this function in the header leads to compiler error on
+ // release-armv5, on at least timebox 9.2 and 10.1.
+ connection = c;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qhttpnetworkconnectionchannel_p.cpp"
+
+#endif // QT_NO_HTTP
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
new file mode 100644
index 0000000000..f27c6f5294
--- /dev/null
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPNETWORKCONNECTIONCHANNEL_H
+#define QHTTPNETWORKCONNECTIONCHANNEL_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>
+
+#include <private/qobject_p.h>
+#include <qauthenticator.h>
+#include <qnetworkproxy.h>
+#include <qbuffer.h>
+
+#include <private/qhttpnetworkheader_p.h>
+#include <private/qhttpnetworkrequest_p.h>
+#include <private/qhttpnetworkreply_p.h>
+
+#include <private/qhttpnetworkconnection_p.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 QByteArray;
+
+#ifndef HttpMessagePair
+typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
+#endif
+
+class QHttpNetworkConnectionChannel : public QObject {
+ Q_OBJECT
+public:
+ enum ChannelState {
+ 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
+ ClosingState = 16,
+ BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|ClosingState)
+ };
+ QAbstractSocket *socket;
+ bool ssl;
+ ChannelState 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 authMethod;
+ QAuthenticatorPrivate::Method proxyAuthMethod;
+ QAuthenticator authenticator;
+ QAuthenticator proxyAuthenticator;
+#ifndef QT_NO_OPENSSL
+ bool ignoreAllSslErrors;
+ QList<QSslError> ignoreSslErrorsList;
+#endif
+#ifndef QT_NO_BEARERMANAGEMENT
+ QSharedPointer<QNetworkSession> networkSession;
+#endif
+
+ // HTTP pipelining -> http://en.wikipedia.org/wiki/Http_pipelining
+ enum PipeliningSupport {
+ PipeliningSupportUnknown, // default for a new connection
+ PipeliningProbablySupported, // after having received a server response that indicates support
+ PipeliningNotSupported // currently not used
+ };
+ PipeliningSupport pipeliningSupported;
+ QList<HttpMessagePair> alreadyPipelinedRequests;
+ QByteArray pipeline; // temporary buffer that gets sent to socket in pipelineFlush
+ void pipelineInto(HttpMessagePair &pair);
+ void pipelineFlush();
+ void requeueCurrentlyPipelinedRequests();
+ void detectPipeliningSupport();
+
+ QHttpNetworkConnectionChannel();
+
+ void setConnection(QHttpNetworkConnection *c);
+ QPointer<QHttpNetworkConnection> connection;
+
+ void init();
+ void close();
+
+ bool sendRequest();
+
+ bool ensureConnection();
+
+ bool expand(bool dataComplete);
+ void allDone(); // reply header + body have been read
+ void handleStatus(); // called from allDone()
+
+ bool resetUploadData(); // return true if resetting worked or there is no upload data
+
+ void handleUnexpectedEOF();
+ void closeAndResendCurrentRequest();
+
+ bool isSocketBusy() const;
+ bool isSocketWriting() const;
+ bool isSocketWaiting() const;
+ bool isSocketReading() const;
+
+ friend class QNetworkAccessHttpBackend;
+
+ protected slots:
+ void _q_receiveReply();
+ void _q_bytesWritten(qint64 bytes); // proceed sending
+ void _q_readyRead(); // pending data to read
+ void _q_disconnected(); // disconnected from host
+ 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_uploadDataReadyRead();
+
+#ifndef QT_NO_OPENSSL
+ void _q_encrypted(); // start sending request (https)
+ void _q_sslErrors(const QList<QSslError> &errors); // ssl errors from the socket
+ void _q_encryptedBytesWritten(qint64 bytes); // proceed sending
+#endif
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP
+
+#endif
diff --git a/src/network/access/qhttpnetworkheader.cpp b/src/network/access/qhttpnetworkheader.cpp
new file mode 100644
index 0000000000..2e33b3736d
--- /dev/null
+++ b/src/network/access/qhttpnetworkheader.cpp
@@ -0,0 +1,134 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpnetworkheader_p.h"
+
+#ifndef QT_NO_HTTP
+
+QT_BEGIN_NAMESPACE
+
+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;
+ // We are not using the headerField() method here because servers might send us multiple content-length
+ // headers which is crap (see QTBUG-15311). Therefore just take the first content-length header field.
+ QByteArray value;
+ QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(),
+ end = fields.constEnd();
+ for ( ; it != end; ++it)
+ if (qstricmp("content-length", it->first) == 0) {
+ value = it->second;
+ break;
+ }
+
+ 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 (const QByteArray &value, allValues) {
+ if (!first)
+ result += ", ";
+ first = false;
+ result += value;
+ }
+ return result;
+}
+
+QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray &name) const
+{
+ QList<QByteArray> result;
+ QList<QPair<QByteArray, QByteArray> >::ConstIterator it = fields.constBegin(),
+ end = fields.constEnd();
+ for ( ; it != end; ++it)
+ if (qstricmp(name.constData(), it->first) == 0)
+ result += it->second;
+
+ return result;
+}
+
+void QHttpNetworkHeaderPrivate::setHeaderField(const QByteArray &name, const QByteArray &data)
+{
+ QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin();
+ while (it != fields.end()) {
+ if (qstricmp(name.constData(), it->first) == 0)
+ it = fields.erase(it);
+ else
+ ++it;
+ }
+ fields.append(qMakePair(name, data));
+}
+
+bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const
+{
+ return (url == other.url);
+}
+
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h
new file mode 100644
index 0000000000..caebf7f730
--- /dev/null
+++ b/src/network/access/qhttpnetworkheader_p.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPNETWORKHEADER_H
+#define QHTTPNETWORKHEADER_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.
+//
+#ifndef QT_NO_HTTP
+
+#include <qshareddata.h>
+#include <qurl.h>
+
+QT_BEGIN_NAMESPACE
+
+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 QHttpNetworkHeaderPrivate : public QSharedData
+{
+public:
+ QUrl url;
+ QList<QPair<QByteArray, QByteArray> > fields;
+
+ QHttpNetworkHeaderPrivate(const QUrl &newUrl = QUrl());
+ QHttpNetworkHeaderPrivate(const QHttpNetworkHeaderPrivate &other);
+ qint64 contentLength() const;
+ void setContentLength(qint64 length);
+
+ QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const;
+ QList<QByteArray> headerFieldValues(const QByteArray &name) const;
+ void setHeaderField(const QByteArray &name, const QByteArray &data);
+ bool operator==(const QHttpNetworkHeaderPrivate &other) const;
+
+};
+
+
+QT_END_NAMESPACE
+
+
+#endif // QT_NO_HTTP
+
+
+#endif // QHTTPNETWORKHEADER_H
+
+
+
+
+
+
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
new file mode 100644
index 0000000000..cb6c09f010
--- /dev/null
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -0,0 +1,951 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpnetworkreply_p.h"
+#include "qhttpnetworkconnection_p.h"
+
+#include <qbytearraymatcher.h>
+
+#ifndef QT_NO_HTTP
+
+#ifndef QT_NO_OPENSSL
+# include <QtNetwork/qsslkey.h>
+# include <QtNetwork/qsslcipher.h>
+# include <QtNetwork/qsslconfiguration.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+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;
+ d->ssl = request.isSsl();
+}
+
+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()->uncompressedBytesAvailable(*this);
+ else
+ return -1;
+}
+
+qint64 QHttpNetworkReply::bytesAvailableNextBlock() const
+{
+ Q_D(const QHttpNetworkReply);
+ if (d->connection)
+ return d->connection->d_func()->uncompressedBytesAvailableNextBlock(*this);
+ else
+ return -1;
+}
+
+bool QHttpNetworkReply::readAnyAvailable() const
+{
+ Q_D(const QHttpNetworkReply);
+ return (d->responseData.bufferCount() > 0);
+}
+
+QByteArray QHttpNetworkReply::readAny()
+{
+ Q_D(QHttpNetworkReply);
+ if (d->responseData.bufferCount() == 0)
+ return QByteArray();
+
+ // we'll take the last buffer, so schedule another read from http
+ if (d->downstreamLimited && d->responseData.bufferCount() == 1)
+ d->connection->d_func()->readMoreLater(this);
+ return d->responseData.read();
+}
+
+QByteArray QHttpNetworkReply::readAll()
+{
+ Q_D(QHttpNetworkReply);
+ return d->responseData.readAll();
+}
+
+void QHttpNetworkReply::setDownstreamLimited(bool dsl)
+{
+ Q_D(QHttpNetworkReply);
+ d->downstreamLimited = dsl;
+ d->connection->d_func()->readMoreLater(this);
+}
+
+bool QHttpNetworkReply::supportsUserProvidedDownloadBuffer()
+{
+ Q_D(QHttpNetworkReply);
+ return (!d->isChunked() && !d->autoDecompress && d->bodyLength > 0);
+}
+
+void QHttpNetworkReply::setUserProvidedDownloadBuffer(char* b)
+{
+ Q_D(QHttpNetworkReply);
+ if (supportsUserProvidedDownloadBuffer())
+ d->userProvidedDownloadBuffer = b;
+}
+
+char* QHttpNetworkReply::userProvidedDownloadBuffer()
+{
+ Q_D(QHttpNetworkReply);
+ return d->userProvidedDownloadBuffer;
+}
+
+bool QHttpNetworkReply::isFinished() const
+{
+ return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState;
+}
+
+bool QHttpNetworkReply::isPipeliningUsed() const
+{
+ return d_func()->pipeliningUsed;
+}
+
+QHttpNetworkConnection* QHttpNetworkReply::connection()
+{
+ return d_func()->connection;
+}
+
+
+QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl)
+ : QHttpNetworkHeaderPrivate(newUrl)
+ , state(NothingDoneState)
+ , ssl(false)
+ , statusCode(100),
+ majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0),
+ chunkedTransferEncoding(false),
+ connectionCloseEnabled(true),
+ forceConnectionCloseEnabled(false),
+ currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false),
+ autoDecompress(false), responseData(), requestIsPrepared(false)
+ ,pipeliningUsed(false), downstreamLimited(false)
+ ,userProvidedDownloadBuffer(0)
+{
+}
+
+QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate()
+{
+}
+
+void QHttpNetworkReplyPrivate::clearHttpLayerInformation()
+{
+ state = NothingDoneState;
+ statusCode = 100;
+ bodyLength = 0;
+ contentRead = 0;
+ totalProgress = 0;
+ currentChunkSize = 0;
+ currentChunkRead = 0;
+ connectionCloseEnabled = true;
+#ifndef QT_NO_COMPRESS
+ if (initInflate)
+ inflateEnd(&inflateStrm);
+#endif
+ initInflate = false;
+ streamEnd = false;
+ fields.clear();
+}
+
+// TODO: Isn't everything HTTP layer related? We don't need to set connection and connectionChannel to 0 at all
+void QHttpNetworkReplyPrivate::clear()
+{
+ connection = 0;
+ connectionChannel = 0;
+ autoDecompress = false;
+ clearHttpLayerInformation();
+}
+
+// QHttpNetworkReplyPrivate
+qint64 QHttpNetworkReplyPrivate::bytesAvailable() const
+{
+ return (state != ReadingDataState ? 0 : fragment.size());
+}
+
+bool QHttpNetworkReplyPrivate::isGzipped()
+{
+ QByteArray encoding = headerField("content-encoding");
+ return qstricmp(encoding.constData(), "gzip") == 0;
+}
+
+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");
+ QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(),
+ end = fields.end();
+ while (it != end) {
+ if (qstricmp(name.constData(), it->first.constData()) == 0) {
+ 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);
+ // todo use qstrincmp
+ 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)
+{
+ if (fragment.isEmpty()) {
+ // reserve bytes for the status line. This is better than always append() which reallocs the byte array
+ fragment.reserve(32);
+ }
+
+ qint64 bytes = 0;
+ char c;
+ qint64 haveRead = 0;
+
+ do {
+ haveRead = socket->read(&c, 1);
+ if (haveRead == -1)
+ return -1; // unexpected EOF
+ else if (haveRead == 0)
+ break; // read more later
+ else if (haveRead == 1 && bytes == 0 && (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31))
+ continue; // Ignore all whitespace that was trailing froma previous request on that socket
+
+ bytes++;
+
+ // allow both CRLF & LF (only) line endings
+ if (c == '\n') {
+ // remove the CR at the end
+ if (fragment.endsWith('\r')) {
+ fragment.truncate(fragment.length()-1);
+ }
+ bool ok = parseStatus(fragment);
+ state = ReadingHeaderState;
+ fragment.clear();
+ if (!ok) {
+ return -1;
+ }
+ break;
+ } else {
+ fragment.append(c);
+ }
+
+ // is this a valid reply?
+ if (fragment.length() >= 5 && !fragment.startsWith("HTTP/"))
+ {
+ fragment.clear();
+ return -1;
+ }
+ } while (haveRead == 1);
+
+ return bytes;
+}
+
+bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status)
+{
+ // from RFC 2616:
+ // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
+ // that makes: 'HTTP/n.n xxx Message'
+ // byte count: 0123456789012
+
+ static const int minLength = 11;
+ static const int dotPos = 6;
+ static const int spacePos = 8;
+ static const char httpMagic[] = "HTTP/";
+
+ if (status.length() < minLength
+ || !status.startsWith(httpMagic)
+ || status.at(dotPos) != '.'
+ || status.at(spacePos) != ' ') {
+ // I don't know how to parse this status line
+ return false;
+ }
+
+ // optimize for the valid case: defer checking until the end
+ majorVersion = status.at(dotPos - 1) - '0';
+ minorVersion = status.at(dotPos + 1) - '0';
+
+ int i = spacePos;
+ int j = status.indexOf(' ', i + 1); // j == -1 || at(j) == ' ' so j+1 == 0 && j+1 <= length()
+ const QByteArray code = status.mid(i + 1, j - i - 1);
+
+ bool ok;
+ statusCode = code.toInt(&ok);
+ reasonPhrase = QString::fromLatin1(status.constData() + j + 1);
+
+ return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9;
+}
+
+qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
+{
+ if (fragment.isEmpty()) {
+ // according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header
+ // block is 381 bytes.
+ // reserve bytes. This is better than always append() which reallocs the byte array.
+ fragment.reserve(512);
+ }
+
+ qint64 bytes = 0;
+ char c = 0;
+ bool allHeaders = false;
+ qint64 haveRead = 0;
+ do {
+ haveRead = socket->read(&c, 1);
+ if (haveRead == 0) {
+ // read more later
+ break;
+ } else if (haveRead == -1) {
+ // connection broke down
+ return -1;
+ } else {
+ fragment.append(c);
+ bytes++;
+
+ if (c == '\n') {
+ // check for possible header endings. As per HTTP rfc,
+ // the header endings will be marked by CRLFCRLF. But
+ // we will allow CRLFCRLF, CRLFLF, LFLF
+ if (fragment.endsWith("\r\n\r\n")
+ || fragment.endsWith("\r\n\n")
+ || fragment.endsWith("\n\n"))
+ allHeaders = true;
+
+ // there is another case: We have no headers. Then the fragment equals just the line ending
+ if ((fragment.length() == 2 && fragment.endsWith("\r\n"))
+ || (fragment.length() == 1 && fragment.endsWith("\n")))
+ allHeaders = true;
+ }
+ }
+ } while (!allHeaders && haveRead > 0);
+
+ // we received all headers now parse them
+ if (allHeaders) {
+ parseHeader(fragment);
+ state = ReadingDataState;
+ fragment.clear(); // next fragment
+ bodyLength = contentLength(); // cache the length
+
+ // cache isChunked() since it is called often
+ chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked");
+
+ // cache isConnectionCloseEnabled since it is called often
+ QByteArray connectionHeaderField = headerField("connection");
+ // check for explicit indication of close or the implicit connection close of HTTP/1.0
+ connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") ||
+ headerField("proxy-connection").toLower().contains("close")) ||
+ (majorVersion == 1 && minorVersion == 0 && connectionHeaderField.isEmpty());
+ }
+ 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 chunkedTransferEncoding;
+}
+
+bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled()
+{
+ return connectionCloseEnabled || forceConnectionCloseEnabled;
+}
+
+// note this function can only be used for non-chunked, non-compressed with
+// known content length
+qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char *b)
+{
+ // This first read is to flush the buffer inside the socket
+ qint64 haveRead = 0;
+ haveRead = socket->read(b, bodyLength - contentRead);
+ if (haveRead == -1) {
+ return 0; // ### error checking here;
+ }
+ contentRead += haveRead;
+
+ if (contentRead == bodyLength) {
+ state = AllDoneState;
+ }
+
+ return haveRead;
+}
+
+// note this function can only be used for non-chunked, non-compressed with
+// known content length
+qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb)
+{
+
+ qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead);
+ QByteArray bd;
+ bd.resize(toBeRead);
+ qint64 haveRead = socket->read(bd.data(), toBeRead);
+ if (haveRead == -1) {
+ bd.clear();
+ return 0; // ### error checking here;
+ }
+ bd.resize(haveRead);
+
+ rb->append(bd);
+
+ if (contentRead + haveRead == bodyLength) {
+ state = AllDoneState;
+ }
+
+ contentRead += haveRead;
+ return haveRead;
+}
+
+
+qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out)
+{
+ qint64 bytes = 0;
+ if (isChunked()) {
+ // chunked transfer encoding (rfc 2616, sec 3.6)
+ bytes += readReplyBodyChunked(socket, out);
+ } else if (bodyLength > 0) {
+ // we have a Content-Length
+ bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead);
+ if (contentRead + bytes == bodyLength)
+ state = AllDoneState;
+ } else {
+ // no content length. just read what's possible
+ bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable());
+ }
+ contentRead += bytes;
+ return bytes;
+}
+
+qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByteDataBuffer *out, qint64 size)
+{
+ // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable()
+ qint64 bytes = 0;
+ Q_ASSERT(socket);
+ Q_ASSERT(out);
+
+ int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable()));
+
+ while (toBeRead > 0) {
+ QByteArray byteData;
+ byteData.resize(toBeRead);
+ qint64 haveRead = socket->read(byteData.data(), byteData.size());
+ if (haveRead <= 0) {
+ // ### error checking here
+ byteData.clear();
+ return bytes;
+ }
+
+ byteData.resize(haveRead);
+ out->append(byteData);
+ bytes += haveRead;
+ size -= haveRead;
+
+ toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable()));
+ }
+ return bytes;
+
+}
+
+qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, QByteDataBuffer *out)
+{
+ qint64 bytes = 0;
+ while (socket->bytesAvailable()) {
+ if (currentChunkRead >= currentChunkSize) {
+ // For the first chunk and when we're done with a chunk
+ currentChunkSize = 0;
+ currentChunkRead = 0;
+ if (bytes) {
+ // After a chunk
+ char crlf[2];
+ // read the "\r\n" after the chunk
+ qint64 haveRead = socket->read(crlf, 2);
+ // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not available yet?!
+ // For nice reasons (the toLong in getChunkSize accepting \n at the beginning
+ // it right now still works, but we should definitely fix this.
+
+ if (haveRead != 2)
+ return bytes; // FIXME
+ bytes += haveRead;
+ }
+ // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes read
+ bytes += getChunkSize(socket, &currentChunkSize);
+ if (currentChunkSize == -1)
+ break;
+ }
+ // if the chunk size is 0, end of the stream
+ if (currentChunkSize == 0) {
+ state = AllDoneState;
+ break;
+ }
+
+ // otherwise, try to begin reading this chunk / to read what is missing for this chunk
+ qint64 haveRead = readReplyBodyRaw (socket, out, currentChunkSize - currentChunkRead);
+ currentChunkRead += haveRead;
+ bytes += haveRead;
+
+ // ### error checking here
+
+ }
+ return bytes;
+}
+
+qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *chunkSize)
+{
+ qint64 bytes = 0;
+ char crlf[2];
+ *chunkSize = -1;
+
+ int bytesAvailable = socket->bytesAvailable();
+ // FIXME rewrite to permanent loop without bytesAvailable
+ while (bytesAvailable > bytes) {
+ qint64 sniffedBytes = socket->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 += socket->read(crlf, 1); // read the \r or \n
+ if (crlf[0] == '\r')
+ bytes += socket->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;
+ qint64 haveRead = socket->read(&c, 1);
+ if (haveRead < 0) {
+ return -1; // FIXME
+ }
+ bytes += haveRead;
+ fragment.append(c);
+ }
+ }
+
+ return bytes;
+}
+
+void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteArray &qba)
+{
+ responseData.append(qba);
+
+ // clear the original! helps with implicit sharing and
+ // avoiding memcpy when the user is reading the data
+ qba.clear();
+}
+
+void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteDataBuffer &data)
+{
+ responseData.append(data);
+
+ // clear the original! helps with implicit sharing and
+ // avoiding memcpy when the user is reading the data
+ data.clear();
+}
+
+void QHttpNetworkReplyPrivate::appendCompressedReplyData(QByteDataBuffer &data)
+{
+ // Work in progress: Later we will directly use a list of QByteArray or a QRingBuffer
+ // instead of one QByteArray.
+ for(int i = 0; i < data.bufferCount(); i++) {
+ QByteArray &byteData = data[i];
+ compressedData.append(byteData.constData(), byteData.size());
+ }
+ data.clear();
+}
+
+
+bool QHttpNetworkReplyPrivate::shouldEmitSignals()
+{
+ // for 401 & 407 don't emit the data signals. Content along with these
+ // responses are send only if the authentication fails.
+ return (statusCode != 401 && statusCode != 407);
+}
+
+bool QHttpNetworkReplyPrivate::expectContent()
+{
+ // check whether we can expect content after the headers (rfc 2616, sec4.4)
+ if ((statusCode >= 100 && statusCode < 200)
+ || statusCode == 204 || statusCode == 304)
+ return false;
+ if (request.operation() == QHttpNetworkRequest::Head)
+ return !shouldEmitSignals();
+ qint64 expectedContentLength = contentLength();
+ if (expectedContentLength == 0)
+ return false;
+ if (expectedContentLength == -1 && bodyLength == 0) {
+ // The content-length header was stripped, but its value was 0.
+ // This would be the case for an explicitly zero-length compressed response.
+ return false;
+ }
+ return true;
+}
+
+void QHttpNetworkReplyPrivate::eraseData()
+{
+ compressedData.clear();
+ responseData.clear();
+}
+
+
+// SSL support below
+#ifndef QT_NO_OPENSSL
+
+QSslConfiguration QHttpNetworkReply::sslConfiguration() const
+{
+ Q_D(const QHttpNetworkReply);
+
+ if (!d->connectionChannel)
+ return QSslConfiguration();
+
+ QSslSocket *sslSocket = qobject_cast<QSslSocket*>(d->connectionChannel->socket);
+ if (!sslSocket)
+ return QSslConfiguration();
+
+ return sslSocket->sslConfiguration();
+}
+
+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();
+}
+
+void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
+{
+ Q_D(QHttpNetworkReply);
+ if (d->connection)
+ d->connection->ignoreSslErrors(errors);
+}
+
+
+#endif //QT_NO_OPENSSL
+
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
new file mode 100644
index 0000000000..cc0f671d2a
--- /dev/null
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -0,0 +1,266 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPNETWORKREPLY_H
+#define QHTTPNETWORKREPLY_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 <qplatformdefs.h>
+#ifndef QT_NO_HTTP
+
+#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
+
+#include <QtNetwork/qtcpsocket.h>
+// it's safe to include these even if SSL support is not enabled
+#include <QtNetwork/qsslsocket.h>
+#include <QtNetwork/qsslerror.h>
+
+#include <QtNetwork/qnetworkrequest.h>
+#include <QtNetwork/qnetworkreply.h>
+#include <qbuffer.h>
+
+#include <private/qobject_p.h>
+#include <private/qhttpnetworkheader_p.h>
+#include <private/qhttpnetworkrequest_p.h>
+#include <private/qauthenticator_p.h>
+#include <private/qringbuffer_p.h>
+#include <private/qbytedata_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QHttpNetworkConnection;
+class QHttpNetworkConnectionChannel;
+class QHttpNetworkRequest;
+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;
+ qint64 bytesAvailableNextBlock() const;
+ bool readAnyAvailable() const;
+ QByteArray readAny();
+ QByteArray readAll();
+ void setDownstreamLimited(bool t);
+
+ bool supportsUserProvidedDownloadBuffer();
+ void setUserProvidedDownloadBuffer(char*);
+ char* userProvidedDownloadBuffer();
+
+ bool isFinished() const;
+
+ bool isPipeliningUsed() const;
+
+ QHttpNetworkConnection* connection();
+
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration sslConfiguration() const;
+ void setSslConfiguration(const QSslConfiguration &config);
+ void ignoreSslErrors();
+ void ignoreSslErrors(const QList<QSslError> &errors);
+
+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();
+ // FIXME we need to change this to qint64!
+ void dataReadProgress(int done, int total);
+ void dataSendProgress(qint64 done, qint64 total);
+ void cacheCredentials(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
+#ifndef QT_NO_NETWORKPROXY
+ void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
+#endif
+ void authenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
+private:
+ Q_DECLARE_PRIVATE(QHttpNetworkReply)
+ friend class QHttpNetworkConnection;
+ friend class QHttpNetworkConnectionPrivate;
+ friend class QHttpNetworkConnectionChannel;
+};
+
+
+class QHttpNetworkReplyPrivate : public QObjectPrivate, public QHttpNetworkHeaderPrivate
+{
+public:
+ QHttpNetworkReplyPrivate(const QUrl &newUrl = QUrl());
+ ~QHttpNetworkReplyPrivate();
+ qint64 readStatus(QAbstractSocket *socket);
+ bool parseStatus(const QByteArray &status);
+ qint64 readHeader(QAbstractSocket *socket);
+ void parseHeader(const QByteArray &header);
+ qint64 readBody(QAbstractSocket *socket, QByteDataBuffer *out);
+ qint64 readBodyVeryFast(QAbstractSocket *socket, char *b);
+ qint64 readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb);
+ bool findChallenge(bool forProxy, QByteArray &challenge) const;
+ QAuthenticatorPrivate::Method authenticationMethod(bool isProxy) const;
+ void clear();
+ void clearHttpLayerInformation();
+
+ qint64 readReplyBodyRaw(QAbstractSocket *in, QByteDataBuffer *out, qint64 size);
+ qint64 readReplyBodyChunked(QAbstractSocket *in, QByteDataBuffer *out);
+ qint64 getChunkSize(QAbstractSocket *in, qint64 *chunkSize);
+
+ void appendUncompressedReplyData(QByteArray &qba);
+ void appendUncompressedReplyData(QByteDataBuffer &data);
+ void appendCompressedReplyData(QByteDataBuffer &data);
+
+ bool shouldEmitSignals();
+ bool expectContent();
+ void eraseData();
+
+ qint64 bytesAvailable() const;
+ bool isChunked();
+ bool isConnectionCloseEnabled();
+ 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;
+ bool ssl;
+ int statusCode;
+ int majorVersion;
+ int minorVersion;
+ QString errorString;
+ QString reasonPhrase;
+ qint64 bodyLength;
+ qint64 contentRead;
+ qint64 totalProgress;
+ QByteArray fragment; // used for header, status, chunk header etc, not for reply data
+ bool chunkedTransferEncoding;
+ bool connectionCloseEnabled;
+ bool forceConnectionCloseEnabled;
+ qint64 currentChunkSize;
+ qint64 currentChunkRead;
+ QPointer<QHttpNetworkConnection> connection;
+ QPointer<QHttpNetworkConnectionChannel> connectionChannel;
+ bool initInflate;
+ bool streamEnd;
+#ifndef QT_NO_COMPRESS
+ z_stream inflateStrm;
+#endif
+ bool autoDecompress;
+
+ QByteDataBuffer responseData; // uncompressed body
+ QByteArray compressedData; // compressed body (temporary)
+ bool requestIsPrepared;
+
+ bool pipeliningUsed;
+ bool downstreamLimited;
+
+ char* userProvidedDownloadBuffer;
+};
+
+
+
+
+QT_END_NAMESPACE
+
+//Q_DECLARE_METATYPE(QHttpNetworkReply)
+
+#endif // QT_NO_HTTP
+
+
+#endif // QHTTPNETWORKREPLY_H
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
new file mode 100644
index 0000000000..2c67e87623
--- /dev/null
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -0,0 +1,325 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qhttpnetworkrequest_p.h"
+#include "private/qnoncontiguousbytedevice_p.h"
+
+#ifndef QT_NO_HTTP
+
+QT_BEGIN_NAMESPACE
+
+QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op,
+ QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
+ : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
+ autoDecompress(false), pipeliningAllowed(false), withCredentials(true)
+{
+}
+
+QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequestPrivate &other)
+ : QHttpNetworkHeaderPrivate(other)
+{
+ operation = other.operation;
+ priority = other.priority;
+ uploadByteDevice = other.uploadByteDevice;
+ autoDecompress = other.autoDecompress;
+ pipeliningAllowed = other.pipeliningAllowed;
+ customVerb = other.customVerb;
+ withCredentials = other.withCredentials;
+ ssl = other.ssl;
+}
+
+QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
+{
+}
+
+bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &other) const
+{
+ return QHttpNetworkHeaderPrivate::operator==(other)
+ && (operation == other.operation)
+ && (ssl == other.ssl)
+ && (uploadByteDevice == other.uploadByteDevice);
+}
+
+QByteArray QHttpNetworkRequestPrivate::methodName() const
+{
+ switch (operation) {
+ case QHttpNetworkRequest::Get:
+ return "GET";
+ break;
+ case QHttpNetworkRequest::Head:
+ return "HEAD";
+ break;
+ case QHttpNetworkRequest::Post:
+ return "POST";
+ break;
+ case QHttpNetworkRequest::Options:
+ return "OPTIONS";
+ break;
+ case QHttpNetworkRequest::Put:
+ return "PUT";
+ break;
+ case QHttpNetworkRequest::Delete:
+ return "DELETE";
+ break;
+ case QHttpNetworkRequest::Trace:
+ return "TRACE";
+ break;
+ case QHttpNetworkRequest::Connect:
+ return "CONNECT";
+ break;
+ case QHttpNetworkRequest::Custom:
+ return customVerb;
+ break;
+ default:
+ break;
+ }
+ return QByteArray();
+}
+
+QByteArray QHttpNetworkRequestPrivate::uri(bool throughProxy) const
+{
+ QUrl::FormattingOptions format(QUrl::RemoveFragment);
+
+ // for POST, query data is send as content
+ if (operation == QHttpNetworkRequest::Post && !uploadByteDevice)
+ 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)
+{
+ QList<QPair<QByteArray, QByteArray> > fields = request.header();
+ QByteArray ba;
+ ba.reserve(40 + fields.length()*25); // very rough lower bound estimation
+
+ ba += request.d->methodName();
+ ba += ' ';
+ ba += request.d->uri(throughProxy);
+
+ ba += " HTTP/";
+ ba += QByteArray::number(request.majorVersion());
+ ba += '.';
+ ba += QByteArray::number(request.minorVersion());
+ ba += "\r\n";
+
+ QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
+ QList<QPair<QByteArray, QByteArray> >::const_iterator endIt = fields.constEnd();
+ for (; it != endIt; ++it) {
+ ba += it->first;
+ ba += ": ";
+ ba += it->second;
+ ba += "\r\n";
+ }
+ if (request.d->operation == QHttpNetworkRequest::Post) {
+ // add content type, if not set in the request
+ if (request.headerField("content-type").isEmpty()) {
+ qWarning("content-type missing in HTTP POST, defaulting to application/octet-stream");
+ ba += "Content-Type: application/octet-stream\r\n";
+ }
+ if (!request.d->uploadByteDevice && request.d->url.hasQuery()) {
+ QByteArray query = request.d->url.encodedQuery();
+ ba += "Content-Length: ";
+ ba += QByteArray::number(query.size());
+ ba += "\r\n\r\n";
+ ba += query;
+ } else {
+ ba += "\r\n";
+ }
+ } else {
+ ba += "\r\n";
+ }
+ return ba;
+}
+
+
+// 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;
+}
+
+bool QHttpNetworkRequest::isSsl() const
+{
+ return d->ssl;
+}
+void QHttpNetworkRequest::setSsl(bool s)
+{
+ d->ssl = s;
+}
+
+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;
+}
+
+QByteArray QHttpNetworkRequest::customVerb() const
+{
+ return d->customVerb;
+}
+
+void QHttpNetworkRequest::setCustomVerb(const QByteArray &customVerb)
+{
+ d->customVerb = customVerb;
+}
+
+QHttpNetworkRequest::Priority QHttpNetworkRequest::priority() const
+{
+ return d->priority;
+}
+
+void QHttpNetworkRequest::setPriority(Priority priority)
+{
+ d->priority = priority;
+}
+
+bool QHttpNetworkRequest::isPipeliningAllowed() const
+{
+ return d->pipeliningAllowed;
+}
+
+void QHttpNetworkRequest::setPipeliningAllowed(bool b)
+{
+ d->pipeliningAllowed = b;
+}
+
+bool QHttpNetworkRequest::withCredentials() const
+{
+ return d->withCredentials;
+}
+
+void QHttpNetworkRequest::setWithCredentials(bool b)
+{
+ d->withCredentials = b;
+}
+
+void QHttpNetworkRequest::setUploadByteDevice(QNonContiguousByteDevice *bd)
+{
+ d->uploadByteDevice = bd;
+}
+
+QNonContiguousByteDevice* QHttpNetworkRequest::uploadByteDevice() const
+{
+ return d->uploadByteDevice;
+}
+
+int QHttpNetworkRequest::majorVersion() const
+{
+ return 1;
+}
+
+int QHttpNetworkRequest::minorVersion() const
+{
+ return 1;
+}
+
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h
new file mode 100644
index 0000000000..c7e9b0f6f5
--- /dev/null
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPNETWORKREQUEST_H
+#define QHTTPNETWORKREQUEST_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.
+//
+#ifndef QT_NO_HTTP
+
+#include <private/qhttpnetworkheader_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QNonContiguousByteDevice;
+
+class QHttpNetworkRequestPrivate;
+class Q_AUTOTEST_EXPORT QHttpNetworkRequest: public QHttpNetworkHeader
+{
+public:
+ enum Operation {
+ Options,
+ Get,
+ Head,
+ Post,
+ Put,
+ Delete,
+ Trace,
+ Connect,
+ Custom
+ };
+
+ 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);
+
+ QByteArray customVerb() const;
+ void setCustomVerb(const QByteArray &customOperation);
+
+ Priority priority() const;
+ void setPriority(Priority priority);
+
+ bool isPipeliningAllowed() const;
+ void setPipeliningAllowed(bool b);
+
+ bool withCredentials() const;
+ void setWithCredentials(bool b);
+
+ bool isSsl() const;
+ void setSsl(bool);
+
+ void setUploadByteDevice(QNonContiguousByteDevice *bd);
+ QNonContiguousByteDevice* uploadByteDevice() const;
+
+private:
+ QSharedDataPointer<QHttpNetworkRequestPrivate> d;
+ friend class QHttpNetworkRequestPrivate;
+ friend class QHttpNetworkConnectionPrivate;
+ friend class QHttpNetworkConnectionChannel;
+};
+
+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;
+ QByteArray customVerb;
+ QHttpNetworkRequest::Priority priority;
+ mutable QNonContiguousByteDevice* uploadByteDevice;
+ bool autoDecompress;
+ bool pipeliningAllowed;
+ bool withCredentials;
+ bool ssl;
+};
+
+
+QT_END_NAMESPACE
+
+//Q_DECLARE_METATYPE(QHttpNetworkRequest)
+
+#endif // QT_NO_HTTP
+
+
+#endif // QHTTPNETWORKREQUEST_H
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
new file mode 100644
index 0000000000..99f9376766
--- /dev/null
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -0,0 +1,579 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#define QHTTPTHREADDELEGATE_DEBUG
+#include "qhttpthreaddelegate_p.h"
+
+#include <QThread>
+#include <QTimer>
+#include <QAuthenticator>
+#include <QEventLoop>
+
+#include "private/qhttpnetworkreply_p.h"
+#include "private/qnetworkaccesscache_p.h"
+#include "private/qnoncontiguousbytedevice_p.h"
+
+
+QT_BEGIN_NAMESPACE
+
+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 405: // Method Not Allowed
+ code = QNetworkReply::ContentOperationNotPermittedError;
+ break;
+
+ case 407:
+ code = QNetworkReply::ProxyAuthenticationRequiredError;
+ break;
+
+ case 418: // I'm a teapot
+ code = QNetworkReply::ProtocolInvalidOperationError;
+ 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;
+}
+
+
+static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy)
+{
+ QByteArray result;
+ QUrl copy = url;
+ bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
+ copy.setPort(copy.port(isEncrypted ? 443 : 80));
+ result = copy.toEncoded(QUrl::RemoveUserInfo | QUrl::RemovePath |
+ QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+#ifndef QT_NO_NETWORKPROXY
+ if (proxy && 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();
+ }
+ }
+#else
+ Q_UNUSED(proxy)
+#endif
+
+ return "http-connection:" + result;
+}
+
+class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
+ public QNetworkAccessCache::CacheableObject
+{
+ // Q_OBJECT
+public:
+#ifdef QT_NO_BEARERMANAGEMENT
+ QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
+ : QHttpNetworkConnection(hostName, port, encrypt)
+#else
+ QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, QSharedPointer<QNetworkSession> networkSession)
+ : QHttpNetworkConnection(hostName, port, encrypt, /*parent=*/0, networkSession)
+#endif
+ {
+ setExpires(true);
+ setShareable(true);
+ }
+
+ virtual void dispose()
+ {
+#if 0 // sample code; do this right with the API
+ Q_ASSERT(!isWorking());
+#endif
+ delete this;
+ }
+};
+
+
+QThreadStorage<QNetworkAccessCache *> QHttpThreadDelegate::connections;
+
+
+QHttpThreadDelegate::~QHttpThreadDelegate()
+{
+ // It could be that the main thread has asked us to shut down, so we need to delete the HTTP reply
+ if (httpReply) {
+ delete httpReply;
+ }
+
+ // Get the object cache that stores our QHttpNetworkConnection objects
+ // and release the entry for this QHttpNetworkConnection
+ if (connections.hasLocalData() && !cacheKey.isEmpty()) {
+ connections.localData()->releaseEntry(cacheKey);
+ }
+}
+
+
+QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
+ QObject(parent)
+ , ssl(false)
+ , downloadBufferMaximumSize(0)
+ , pendingDownloadData(0)
+ , pendingDownloadProgress(0)
+ , synchronous(false)
+ , incomingStatusCode(0)
+ , isPipeliningUsed(false)
+ , incomingContentLength(-1)
+ , incomingErrorCode(QNetworkReply::NoError)
+ , downloadBuffer(0)
+ , httpConnection(0)
+ , httpReply(0)
+ , synchronousRequestLoop(0)
+{
+}
+
+// This is invoked as BlockingQueuedConnection from QNetworkAccessHttpBackend in the user thread
+void QHttpThreadDelegate::startRequestSynchronously()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId();
+#endif
+ synchronous = true;
+
+ QEventLoop synchronousRequestLoop;
+ this->synchronousRequestLoop = &synchronousRequestLoop;
+
+ // Worst case timeout
+ QTimer::singleShot(30*1000, this, SLOT(abortRequest()));
+
+ QMetaObject::invokeMethod(this, "startRequest", Qt::QueuedConnection);
+ synchronousRequestLoop.exec();
+
+ connections.localData()->releaseEntry(cacheKey);
+ connections.setLocalData(0);
+
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::startRequestSynchronously() thread=" << QThread::currentThreadId() << "finished";
+#endif
+}
+
+
+// This is invoked as QueuedConnection from QNetworkAccessHttpBackend in the user thread
+void QHttpThreadDelegate::startRequest()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::startRequest() thread=" << QThread::currentThreadId();
+#endif
+ // Check QThreadStorage for the QNetworkAccessCache
+ // If not there, create this connection cache
+ if (!connections.hasLocalData()) {
+ connections.setLocalData(new QNetworkAccessCache());
+ }
+
+ // check if we have an open connection to this host
+ QUrl urlCopy = httpRequest.url();
+ urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
+
+#ifndef QT_NO_NETWORKPROXY
+ if (transparentProxy.type() != QNetworkProxy::NoProxy)
+ cacheKey = makeCacheKey(urlCopy, &transparentProxy);
+ else if (cacheProxy.type() != QNetworkProxy::NoProxy)
+ cacheKey = makeCacheKey(urlCopy, &cacheProxy);
+ else
+#endif
+ cacheKey = makeCacheKey(urlCopy, 0);
+
+
+ // the http object is actually a QHttpNetworkConnection
+ httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
+ if (httpConnection == 0) {
+ // no entry in cache; create an object
+ // the http object is actually a QHttpNetworkConnection
+#ifdef QT_NO_BEARERMANAGEMENT
+ httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl);
+#else
+ httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, networkSession);
+#endif
+#ifndef QT_NO_OPENSSL
+ // Set the QSslConfiguration from this QNetworkRequest.
+ if (ssl && incomingSslConfiguration != QSslConfiguration::defaultConfiguration()) {
+ httpConnection->setSslConfiguration(incomingSslConfiguration);
+ }
+#endif
+
+#ifndef QT_NO_NETWORKPROXY
+ httpConnection->setTransparentProxy(transparentProxy);
+ httpConnection->setCacheProxy(cacheProxy);
+#endif
+
+ // cache the QHttpNetworkConnection corresponding to this cache key
+ connections.localData()->addEntry(cacheKey, httpConnection);
+ }
+
+
+ // Send the request to the connection
+ httpReply = httpConnection->sendRequest(httpRequest);
+ httpReply->setParent(this);
+
+ // Connect the reply signals that we need to handle and then forward
+ if (synchronous) {
+ connect(httpReply,SIGNAL(headerChanged()), this, SLOT(synchronousHeaderChangedSlot()));
+ connect(httpReply,SIGNAL(finished()), this, SLOT(synchronousFinishedSlot()));
+ connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
+ this, SLOT(synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
+
+ connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
+ connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
+
+ // Don't care about ignored SSL errors for now in the synchronous HTTP case.
+ } else if (!synchronous) {
+ connect(httpReply,SIGNAL(headerChanged()), this, SLOT(headerChangedSlot()));
+ connect(httpReply,SIGNAL(finished()), this, SLOT(finishedSlot()));
+ connect(httpReply,SIGNAL(finishedWithError(QNetworkReply::NetworkError, const QString)),
+ this, SLOT(finishedWithErrorSlot(QNetworkReply::NetworkError,QString)));
+ // some signals are only interesting when normal asynchronous style is used
+ connect(httpReply,SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
+ connect(httpReply,SIGNAL(dataReadProgress(int, int)), this, SLOT(dataReadProgressSlot(int,int)));
+ connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
+ this, SLOT(cacheCredentialsSlot(QHttpNetworkRequest,QAuthenticator*)));
+#ifndef QT_NO_OPENSSL
+ connect(httpReply,SIGNAL(sslErrors(const QList<QSslError>)), this, SLOT(sslErrorsSlot(QList<QSslError>)));
+#endif
+
+ // In the asynchronous HTTP case we can just forward those signals
+ // Connect the reply signals that we can directly forward
+ connect(httpReply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ this, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
+ connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ this, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
+ }
+}
+
+// This gets called from the user thread or by the synchronous HTTP timeout timer
+void QHttpThreadDelegate::abortRequest()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::abortRequest() thread=" << QThread::currentThreadId() << "sync=" << synchronous;
+#endif
+ if (httpReply) {
+ delete httpReply;
+ httpReply = 0;
+ }
+
+ // Got aborted by the timeout timer
+ if (synchronous) {
+ incomingErrorCode = QNetworkReply::TimeoutError;
+ QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
+ } else {
+ //only delete this for asynchronous mode or QNetworkAccessHttpBackend will crash - see QNetworkAccessHttpBackend::postRequest()
+ this->deleteLater();
+ }
+}
+
+void QHttpThreadDelegate::readyReadSlot()
+{
+ // Don't do in zerocopy case
+ if (!downloadBuffer.isNull())
+ return;
+
+ while (httpReply->readAnyAvailable()) {
+ pendingDownloadData->fetchAndAddRelease(1);
+ emit downloadData(httpReply->readAny());
+ }
+}
+
+void QHttpThreadDelegate::finishedSlot()
+{
+ if (!httpReply) {
+ qWarning() << "QHttpThreadDelegate::finishedSlot: HTTP reply had already been deleted, internal problem. Please report.";
+ return;
+ }
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::finishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
+#endif
+
+ // If there is still some data left emit that now
+ while (httpReply->readAnyAvailable()) {
+ pendingDownloadData->fetchAndAddRelease(1);
+ emit downloadData(httpReply->readAny());
+ }
+
+#ifndef QT_NO_OPENSSL
+ if (ssl)
+ emit sslConfigurationChanged(httpReply->sslConfiguration());
+#endif
+
+ if (httpReply->statusCode() >= 400) {
+ // it's an error reply
+ QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
+ "Error downloading %1 - server replied: %2"));
+ msg = msg.arg(QString::fromAscii(httpRequest.url().toEncoded()), httpReply->reasonPhrase());
+ emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
+ }
+
+ emit downloadFinished();
+
+ QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
+ httpReply = 0;
+}
+
+void QHttpThreadDelegate::synchronousFinishedSlot()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::synchronousFinishedSlot() thread=" << QThread::currentThreadId() << "result=" << httpReply->statusCode();
+#endif
+ if (httpReply->statusCode() >= 400) {
+ // it's an error reply
+ QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
+ "Error downloading %1 - server replied: %2"));
+ incomingErrorDetail = msg.arg(QString::fromAscii(httpRequest.url().toEncoded()), httpReply->reasonPhrase());
+ incomingErrorCode = statusCodeFromHttp(httpReply->statusCode(), httpRequest.url());
+ }
+
+ synchronousDownloadData = httpReply->readAll();
+
+ QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
+ httpReply = 0;
+}
+
+void QHttpThreadDelegate::finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
+{
+ if (!httpReply) {
+ qWarning() << "QHttpThreadDelegate::finishedWithErrorSlot: HTTP reply had already been deleted, internal problem. Please report.";
+ return;
+ }
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::finishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
+#endif
+
+#ifndef QT_NO_OPENSSL
+ if (ssl)
+ emit sslConfigurationChanged(httpReply->sslConfiguration());
+#endif
+ emit error(errorCode,detail);
+ emit downloadFinished();
+
+
+ QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
+ httpReply = 0;
+}
+
+
+void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail)
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::synchronousFinishedWithErrorSlot() thread=" << QThread::currentThreadId() << "error=" << errorCode << detail;
+#endif
+ incomingErrorCode = errorCode;
+ incomingErrorDetail = detail;
+
+ QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(synchronousRequestLoop, "quit", Qt::QueuedConnection);
+ httpReply = 0;
+}
+
+static void downloadBufferDeleter(char *ptr)
+{
+ delete[] ptr;
+}
+
+void QHttpThreadDelegate::headerChangedSlot()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::headerChangedSlot() thread=" << QThread::currentThreadId();
+#endif
+
+#ifndef QT_NO_OPENSSL
+ if (ssl)
+ emit sslConfigurationChanged(httpReply->sslConfiguration());
+#endif
+
+ // Is using a zerocopy buffer allowed by user and possible with this reply?
+ if (httpReply->supportsUserProvidedDownloadBuffer()
+ && downloadBufferMaximumSize > 0) {
+ char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
+ if (buf) {
+ downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
+ httpReply->setUserProvidedDownloadBuffer(buf);
+ }
+ }
+
+ // We fetch this into our own
+ incomingHeaders = httpReply->header();
+ incomingStatusCode = httpReply->statusCode();
+ incomingReasonPhrase = httpReply->reasonPhrase();
+ isPipeliningUsed = httpReply->isPipeliningUsed();
+ incomingContentLength = httpReply->contentLength();
+
+ emit downloadMetaData(incomingHeaders,
+ incomingStatusCode,
+ incomingReasonPhrase,
+ isPipeliningUsed,
+ downloadBuffer,
+ incomingContentLength);
+}
+
+void QHttpThreadDelegate::synchronousHeaderChangedSlot()
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::synchronousHeaderChangedSlot() thread=" << QThread::currentThreadId();
+#endif
+ // Store the information we need in this object, the QNetworkAccessHttpBackend will later read it
+ incomingHeaders = httpReply->header();
+ incomingStatusCode = httpReply->statusCode();
+ incomingReasonPhrase = httpReply->reasonPhrase();
+ isPipeliningUsed = httpReply->isPipeliningUsed();
+ incomingContentLength = httpReply->contentLength();
+}
+
+
+void QHttpThreadDelegate::dataReadProgressSlot(int done, int total)
+{
+ // If we don't have a download buffer don't attempt to go this codepath
+ // It is not used by QNetworkAccessHttpBackend
+ if (downloadBuffer.isNull())
+ return;
+
+ pendingDownloadProgress->fetchAndAddRelease(1);
+ emit downloadProgress(done, total);
+}
+
+void QHttpThreadDelegate::cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator)
+{
+ authenticationManager->cacheCredentials(request.url(), authenticator);
+}
+
+
+#ifndef QT_NO_OPENSSL
+void QHttpThreadDelegate::sslErrorsSlot(const QList<QSslError> &errors)
+{
+ emit sslConfigurationChanged(httpReply->sslConfiguration());
+
+ bool ignoreAll = false;
+ QList<QSslError> specificErrors;
+ emit sslErrors(errors, &ignoreAll, &specificErrors);
+ if (ignoreAll)
+ httpReply->ignoreSslErrors();
+ if (!specificErrors.isEmpty())
+ httpReply->ignoreSslErrors(specificErrors);
+}
+#endif
+
+void QHttpThreadDelegate::synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *a)
+{
+ Q_UNUSED(request);
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::synchronousAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
+#endif
+
+ // Ask the credential cache
+ QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), a);
+ if (!credential.isNull()) {
+ a->setUser(credential.user);
+ a->setPassword(credential.password);
+ }
+
+ // Disconnect this connection now since we only want to ask the authentication cache once.
+ QObject::disconnect(this, SLOT(synchronousAuthenticationRequiredSlot(QHttpNetworkRequest,QAuthenticator*)));
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &p, QAuthenticator *a)
+{
+#ifdef QHTTPTHREADDELEGATE_DEBUG
+ qDebug() << "QHttpThreadDelegate::synchronousProxyAuthenticationRequiredSlot() thread=" << QThread::currentThreadId();
+#endif
+ // Ask the credential cache
+ QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedProxyCredentials(p, a);
+ if (!credential.isNull()) {
+ a->setUser(credential.user);
+ a->setPassword(credential.password);
+ }
+
+ // Disconnect this connection now since we only want to ask the authentication cache once.
+ QObject::disconnect(this, SLOT(synchronousProxyAuthenticationRequiredSlot(QNetworkProxy,QAuthenticator*)));
+}
+
+#endif
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
new file mode 100644
index 0000000000..752bc099e7
--- /dev/null
+++ b/src/network/access/qhttpthreaddelegate_p.h
@@ -0,0 +1,288 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QHTTPTHREADDELEGATE_H
+#define QHTTPTHREADDELEGATE_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 <QObject>
+#include <QThreadStorage>
+#include <QNetworkProxy>
+#include <QSslConfiguration>
+#include <QSslError>
+#include <QList>
+#include <QNetworkReply>
+#include "qhttpnetworkrequest_p.h"
+#include "qhttpnetworkconnection_p.h"
+#include <QSharedPointer>
+#include "qsslconfiguration.h"
+#include "private/qnoncontiguousbytedevice_p.h"
+#include "qnetworkaccessauthenticationmanager_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAuthenticator;
+class QHttpNetworkReply;
+class QEventLoop;
+class QNetworkAccessCache;
+class QNetworkAccessCachedHttpConnection;
+
+class QHttpThreadDelegate : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QHttpThreadDelegate(QObject *parent = 0);
+
+ ~QHttpThreadDelegate();
+
+ // incoming
+ bool ssl;
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration incomingSslConfiguration;
+#endif
+ QHttpNetworkRequest httpRequest;
+ qint64 downloadBufferMaximumSize;
+ // From backend, modified by us for signal compression
+ QSharedPointer<QAtomicInt> pendingDownloadData;
+ QSharedPointer<QAtomicInt> pendingDownloadProgress;
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy cacheProxy;
+ QNetworkProxy transparentProxy;
+#endif
+ QSharedPointer<QNetworkAccessAuthenticationManager> authenticationManager;
+ bool synchronous;
+
+ // outgoing, Retrieved in the synchronous HTTP case
+ QByteArray synchronousDownloadData;
+ QList<QPair<QByteArray,QByteArray> > incomingHeaders;
+ int incomingStatusCode;
+ QString incomingReasonPhrase;
+ bool isPipeliningUsed;
+ qint64 incomingContentLength;
+ QNetworkReply::NetworkError incomingErrorCode;
+ QString incomingErrorDetail;
+#ifndef QT_NO_BEARERMANAGEMENT
+ QSharedPointer<QNetworkSession> networkSession;
+#endif
+
+protected:
+ // The zerocopy download buffer, if used:
+ QSharedPointer<char> downloadBuffer;
+ // The QHttpNetworkConnection that is used
+ QNetworkAccessCachedHttpConnection *httpConnection;
+ QByteArray cacheKey;
+ QHttpNetworkReply *httpReply;
+
+ // Used for implementing the synchronous HTTP, see startRequestSynchronously()
+ QEventLoop *synchronousRequestLoop;
+
+signals:
+ void authenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *);
+#ifndef QT_NO_NETWORKPROXY
+ void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *);
+#endif
+#ifndef QT_NO_OPENSSL
+ void sslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
+ void sslConfigurationChanged(const QSslConfiguration);
+#endif
+ void downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64);
+ void downloadProgress(qint64, qint64);
+ void downloadData(QByteArray);
+ void error(QNetworkReply::NetworkError, const QString);
+ void downloadFinished();
+public slots:
+ // This are called via QueuedConnection from user thread
+ void startRequest();
+ void abortRequest();
+ // This is called with a BlockingQueuedConnection from user thread
+ void startRequestSynchronously();
+protected slots:
+ // From QHttp*
+ void readyReadSlot();
+ void finishedSlot();
+ void finishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail = QString());
+ void synchronousFinishedSlot();
+ void synchronousFinishedWithErrorSlot(QNetworkReply::NetworkError errorCode, const QString &detail = QString());
+ void headerChangedSlot();
+ void synchronousHeaderChangedSlot();
+ void dataReadProgressSlot(int done, int total);
+ void cacheCredentialsSlot(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
+#ifndef QT_NO_OPENSSL
+ void sslErrorsSlot(const QList<QSslError> &errors);
+#endif
+
+ void synchronousAuthenticationRequiredSlot(const QHttpNetworkRequest &request, QAuthenticator *);
+#ifndef QT_NO_NETWORKPROXY
+ void synchronousProxyAuthenticationRequiredSlot(const QNetworkProxy &, QAuthenticator *);
+#endif
+
+protected:
+ // Cache for all the QHttpNetworkConnection objects.
+ // This is per thread.
+ static QThreadStorage<QNetworkAccessCache *> connections;
+
+};
+
+// This QNonContiguousByteDevice is connected to the QNetworkAccessHttpBackend
+// and represents the PUT/POST data.
+class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevice
+{
+ Q_OBJECT
+protected:
+ bool wantDataPending;
+ qint64 m_amount;
+ char *m_data;
+ QByteArray m_dataArray;
+ bool m_atEnd;
+ qint64 m_size;
+public:
+ QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
+ : QNonContiguousByteDevice(),
+ wantDataPending(false),
+ m_amount(0),
+ m_data(0),
+ m_atEnd(aE),
+ m_size(s)
+ {
+ }
+
+ ~QNonContiguousByteDeviceThreadForwardImpl()
+ {
+ }
+
+ const char* readPointer(qint64 maximumLength, qint64 &len)
+ {
+ if (m_amount == 0 && wantDataPending == false) {
+ len = 0;
+ wantDataPending = true;
+ emit wantData(maximumLength);
+ } else if (m_amount == 0 && wantDataPending == true) {
+ // Do nothing, we already sent a wantData signal and wait for results
+ len = 0;
+ } else if (m_amount > 0) {
+ len = m_amount;
+ return m_data;
+ }
+ // cannot happen
+ return 0;
+ }
+
+ bool advanceReadPointer(qint64 a)
+ {
+ if (m_data == 0)
+ return false;
+
+ m_amount -= a;
+ m_data += a;
+
+ // To main thread to inform about our state
+ emit processedData(a);
+
+ // FIXME possible optimization, already ask user thread for some data
+
+ return true;
+ }
+
+ bool atEnd()
+ {
+ if (m_amount > 0)
+ return false;
+ else
+ return m_atEnd;
+ }
+
+ bool reset()
+ {
+ m_amount = 0;
+ m_data = 0;
+
+ // Communicate as BlockingQueuedConnection
+ bool b = false;
+ emit resetData(&b);
+ return b;
+ }
+
+ qint64 size()
+ {
+ return m_size;
+ }
+
+public slots:
+ // From user thread:
+ void haveDataSlot(QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
+ {
+ wantDataPending = false;
+
+ m_dataArray = dataArray;
+ m_data = const_cast<char*>(m_dataArray.constData());
+ m_amount = dataArray.size();
+
+ m_atEnd = dataAtEnd;
+ m_size = dataSize;
+
+ // This will tell the HTTP code (QHttpNetworkConnectionChannel) that we have data available now
+ emit readyRead();
+ }
+
+signals:
+ // void readyRead(); in parent class
+ // void readProgress(qint64 current, qint64 total); happens in the main thread with the real bytedevice
+
+ // to main thread:
+ void wantData(qint64);
+ void processedData(qint64);
+ void resetData(bool *b);
+};
+
+QT_END_NAMESPACE
+
+#endif // QHTTPTHREADDELEGATE_H
diff --git a/src/network/access/qnetworkaccessauthenticationmanager.cpp b/src/network/access/qnetworkaccessauthenticationmanager.cpp
new file mode 100644
index 0000000000..d2bf00accf
--- /dev/null
+++ b/src/network/access/qnetworkaccessauthenticationmanager.cpp
@@ -0,0 +1,297 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkaccessmanager.h"
+#include "qnetworkaccessmanager_p.h"
+#include "qnetworkaccessauthenticationmanager_p.h"
+
+#include "QtCore/qbuffer.h"
+#include "QtCore/qurl.h"
+#include "QtCore/qvector.h"
+#include "QtCore/QMutexLocker"
+#include "QtNetwork/qauthenticator.h"
+
+QT_BEGIN_NAMESPACE
+
+
+
+
+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"));
+ break;
+
+ 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);
+}
+
+
+#ifndef QT_NO_NETWORKPROXY
+void QNetworkAccessAuthenticationManager::cacheProxyCredentials(const QNetworkProxy &p,
+ const QAuthenticator *authenticator)
+{
+ Q_ASSERT(authenticator);
+ Q_ASSERT(p.type() != QNetworkProxy::DefaultProxy);
+ Q_ASSERT(p.type() != QNetworkProxy::NoProxy);
+
+ QMutexLocker mutexLocker(&mutex);
+
+ 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());
+ authenticationCache.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
+QNetworkAccessAuthenticationManager::fetchCachedProxyCredentials(const QNetworkProxy &p,
+ const QAuthenticator *authenticator)
+{
+ QNetworkProxy proxy = p;
+ if (proxy.type() == QNetworkProxy::DefaultProxy) {
+ proxy = QNetworkProxy::applicationProxy();
+ }
+ if (!proxy.password().isEmpty())
+ return QNetworkAuthenticationCredential(); // no need to set credentials if it already has them
+
+ QString realm;
+ if (authenticator)
+ realm = authenticator->realm();
+
+ QMutexLocker mutexLocker(&mutex);
+ QByteArray cacheKey = proxyAuthenticationKey(proxy, realm);
+ if (cacheKey.isEmpty())
+ return QNetworkAuthenticationCredential();
+ if (!authenticationCache.hasEntry(cacheKey))
+ return QNetworkAuthenticationCredential();
+
+ QNetworkAuthenticationCache *auth =
+ static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(cacheKey));
+ QNetworkAuthenticationCredential cred = *auth->findClosestMatch(QString());
+ authenticationCache.releaseEntry(cacheKey);
+
+ // proxy cache credentials always have exactly one item
+ Q_ASSERT_X(!cred.isNull(), "QNetworkAccessManager",
+ "Internal inconsistency: found a cache key for a proxy, but it's empty");
+ return cred;
+}
+
+#endif
+
+void QNetworkAccessAuthenticationManager::cacheCredentials(const QUrl &url,
+ const QAuthenticator *authenticator)
+{
+ Q_ASSERT(authenticator);
+ QString domain = QString::fromLatin1("/"); // FIXME: make QAuthenticator return the domain
+ QString realm = authenticator->realm();
+
+ QMutexLocker mutexLocker(&mutex);
+
+ // 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 (authenticationCache.hasEntry(cacheKey)) {
+ QNetworkAuthenticationCache *auth =
+ static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(cacheKey));
+ auth->insert(domain, authenticator->user(), authenticator->password());
+ authenticationCache.releaseEntry(cacheKey);
+ } else {
+ QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache;
+ auth->insert(domain, authenticator->user(), authenticator->password());
+ authenticationCache.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
+QNetworkAccessAuthenticationManager::fetchCachedCredentials(const QUrl &url,
+ const QAuthenticator *authentication)
+{
+ if (!url.password().isEmpty())
+ return QNetworkAuthenticationCredential(); // no need to set credentials if it already has them
+
+ QString realm;
+ if (authentication)
+ realm = authentication->realm();
+
+ QByteArray cacheKey = authenticationKey(url, realm);
+
+ QMutexLocker mutexLocker(&mutex);
+ if (!authenticationCache.hasEntry(cacheKey))
+ return QNetworkAuthenticationCredential();
+
+ QNetworkAuthenticationCache *auth =
+ static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(cacheKey));
+ QNetworkAuthenticationCredential cred = *auth->findClosestMatch(url.path());
+ authenticationCache.releaseEntry(cacheKey);
+ return cred;
+}
+
+void QNetworkAccessAuthenticationManager::clearCache()
+{
+ authenticationCache.clear();
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/network/access/qnetworkaccessauthenticationmanager_p.h b/src/network/access/qnetworkaccessauthenticationmanager_p.h
new file mode 100644
index 0000000000..d2347b1722
--- /dev/null
+++ b/src/network/access/qnetworkaccessauthenticationmanager_p.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKACCESSAUTHENTICATIONMANAGER_P_H
+#define QNETWORKACCESSAUTHENTICATIONMANAGER_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 "QtNetwork/qnetworkproxy.h"
+#include "QtCore/QMutex"
+
+QT_BEGIN_NAMESPACE
+
+class QAuthenticator;
+class QAbstractNetworkCache;
+class QNetworkAuthenticationCredential;
+class QNetworkCookieJar;
+
+class QNetworkAuthenticationCredential
+{
+public:
+ QString domain;
+ QString user;
+ QString password;
+ bool isNull() {
+ return domain.isNull();
+ }
+};
+Q_DECLARE_TYPEINFO(QNetworkAuthenticationCredential, Q_MOVABLE_TYPE);
+inline bool operator<(const QNetworkAuthenticationCredential &t1, const QString &t2)
+{ return t1.domain < t2; }
+
+class QNetworkAccessAuthenticationManager
+{
+public:
+ QNetworkAccessAuthenticationManager() { };
+
+ void cacheCredentials(const QUrl &url, const QAuthenticator *auth);
+ QNetworkAuthenticationCredential fetchCachedCredentials(const QUrl &url,
+ const QAuthenticator *auth = 0);
+
+#ifndef QT_NO_NETWORKPROXY
+ void cacheProxyCredentials(const QNetworkProxy &proxy, const QAuthenticator *auth);
+ QNetworkAuthenticationCredential fetchCachedProxyCredentials(const QNetworkProxy &proxy,
+ const QAuthenticator *auth = 0);
+#endif
+
+ void clearCache();
+
+protected:
+ QNetworkAccessCache authenticationCache;
+ QMutex mutex;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp
new file mode 100644
index 0000000000..6220abed02
--- /dev/null
+++ b/src/network/access/qnetworkaccessbackend.cpp
@@ -0,0 +1,382 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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 "QtNetwork/private/qnetworksession_p.h"
+
+#include "qnetworkaccesscachebackend_p.h"
+#include "qabstractnetworkcache.h"
+#include "qhostinfo.h"
+
+#include "private/qnoncontiguousbytedevice_p.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()->append(this);
+}
+
+QNetworkAccessBackendFactory::~QNetworkAccessBackendFactory()
+{
+ if (!factoryDataShutdown) {
+ QMutexLocker locker(&factoryData()->mutex);
+ factoryData()->removeAll(this);
+ }
+}
+
+QNetworkAccessBackend *QNetworkAccessManagerPrivate::findBackend(QNetworkAccessManager::Operation op,
+ const QNetworkRequest &request)
+{
+ 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;
+}
+
+QNonContiguousByteDevice* QNetworkAccessBackend::createUploadByteDevice()
+{
+ if (reply->outgoingDataBuffer)
+ uploadByteDevice = QSharedPointer<QNonContiguousByteDevice>(QNonContiguousByteDeviceFactory::create(reply->outgoingDataBuffer));
+ else if (reply->outgoingData) {
+ uploadByteDevice = QSharedPointer<QNonContiguousByteDevice>(QNonContiguousByteDeviceFactory::create(reply->outgoingData));
+ } else {
+ return 0;
+ }
+
+ bool bufferDisallowed =
+ reply->request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
+ QVariant(false)) == QVariant(true);
+ if (bufferDisallowed)
+ uploadByteDevice->disableReset();
+
+ // We want signal emissions only for normal asynchronous uploads
+ if (!isSynchronous())
+ connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)), this, SLOT(emitReplyUploadProgress(qint64,qint64)));
+
+ return uploadByteDevice.data();
+}
+
+// need to have this function since the reply is a private member variable
+// and the special backends need to access this.
+void QNetworkAccessBackend::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
+{
+ if (reply->isFinished)
+ return;
+ reply->emitUploadProgress(bytesSent, bytesTotal);
+}
+
+QNetworkAccessBackend::QNetworkAccessBackend()
+ : manager(0)
+ , reply(0)
+ , synchronous(false)
+{
+}
+
+QNetworkAccessBackend::~QNetworkAccessBackend()
+{
+}
+
+void QNetworkAccessBackend::downstreamReadyWrite()
+{
+ // do nothing
+}
+
+void QNetworkAccessBackend::setDownstreamLimited(bool b)
+{
+ Q_UNUSED(b);
+ // do nothing
+}
+
+void QNetworkAccessBackend::copyFinished(QIODevice *)
+{
+ // do nothing
+}
+
+void QNetworkAccessBackend::ignoreSslErrors()
+{
+ // do nothing
+}
+
+void QNetworkAccessBackend::ignoreSslErrors(const QList<QSslError> &errors)
+{
+ Q_UNUSED(errors);
+ // 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
+{
+ if (!manager)
+ return 0;
+ return manager->networkCache;
+}
+
+void QNetworkAccessBackend::setCachingEnabled(bool enable)
+{
+ reply->setCachingEnabled(enable);
+}
+
+bool QNetworkAccessBackend::isCachingEnabled() const
+{
+ return reply->isCachingEnabled();
+}
+
+qint64 QNetworkAccessBackend::nextDownstreamBlockSize() const
+{
+ return reply->nextDownstreamBlockSize();
+}
+
+void QNetworkAccessBackend::writeDownstreamData(QByteDataBuffer &list)
+{
+ reply->appendDownstreamData(list);
+}
+
+void QNetworkAccessBackend::writeDownstreamData(QIODevice *data)
+{
+ reply->appendDownstreamData(data);
+}
+
+// not actually appending data, it was already written to the user buffer
+void QNetworkAccessBackend::writeDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal)
+{
+ reply->appendDownstreamDataDownloadBuffer(bytesReceived, bytesTotal);
+}
+
+char* QNetworkAccessBackend::getDownloadBuffer(qint64 size)
+{
+ return reply->getDownloadBuffer(size);
+}
+
+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
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+
+/*!
+ Starts the backend. Returns true if the backend is started. Returns false if the backend
+ could not be started due to an unopened or roaming session. The caller should recall this
+ function once the session has been opened or the roaming process has finished.
+*/
+bool QNetworkAccessBackend::start()
+{
+ if (!manager->networkSession) {
+ open();
+ return true;
+ }
+
+ // This is not ideal.
+ const QString host = reply->url.host();
+ if (host == QLatin1String("localhost") ||
+ QHostAddress(host) == QHostAddress::LocalHost ||
+ QHostAddress(host) == QHostAddress::LocalHostIPv6) {
+ // Don't need an open session for localhost access.
+ open();
+ return true;
+ }
+
+ if (manager->networkSession->isOpen() &&
+ manager->networkSession->state() == QNetworkSession::Connected) {
+ //copy network session down to the backend
+ setProperty("_q_networksession", QVariant::fromValue(manager->networkSession));
+ open();
+ return true;
+ }
+
+ return false;
+}
+#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 0000000000..644ae2d9c7
--- /dev/null
+++ b/src/network/access/qnetworkaccessbackend_p.h
@@ -0,0 +1,230 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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;
+class QNonContiguousByteDevice;
+
+// 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:
+ // The upstream uses a QNonContiguousByteDevice provided
+ // by the backend. This device emits the usual readyRead()
+ // signal when the backend has data available for the connection
+ // to write. The different backends can listen on this signal
+ // and then pull upload data from the QNonContiguousByteDevice and
+ // deal with it.
+ //
+ //
+ // - 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;
+#ifndef QT_NO_BEARERMANAGEMENT
+ virtual bool start();
+#endif
+ virtual void closeDownstreamChannel() = 0;
+
+ // slot-like:
+ virtual void downstreamReadyWrite();
+ virtual void setDownstreamLimited(bool b);
+ virtual void copyFinished(QIODevice *);
+ virtual void ignoreSslErrors();
+ virtual void ignoreSslErrors(const QList<QSslError> &errors);
+
+ 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);
+
+ bool isSynchronous() { return synchronous; }
+ void setSynchronous(bool sync) { synchronous = sync; }
+
+ // return true if the QNonContiguousByteDevice of the upload
+ // data needs to support reset(). Currently needed for HTTP.
+ // This will possibly enable buffering of the upload data.
+ virtual bool needsResetableUploadData() { return false; }
+
+ // Returns true if backend is able to resume downloads.
+ virtual bool canResume() const { return false; }
+ virtual void setResumeOffset(quint64 offset) { Q_UNUSED(offset); }
+
+ virtual bool processRequestSynchronously() { return false; }
+
+protected:
+ // Create the device used for reading the upload data
+ QNonContiguousByteDevice* createUploadByteDevice();
+
+ // these functions control the downstream mechanism
+ // that is, data that has come via the connection and is going out the backend
+ qint64 nextDownstreamBlockSize() const;
+ void writeDownstreamData(QByteDataBuffer &list);
+
+ // not actually appending data, it was already written to the user buffer
+ void writeDownstreamDataDownloadBuffer(qint64, qint64);
+ char* getDownloadBuffer(qint64);
+
+ QSharedPointer<QNonContiguousByteDevice> uploadByteDevice;
+
+public slots:
+ // for task 251801, needs to be a slot to be called asynchronously
+ 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);
+ void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal);
+
+protected:
+ // FIXME In the long run we should get rid of our QNAM architecture
+ // and scrap this ReplyImpl/Backend distinction.
+ QNetworkAccessManagerPrivate *manager;
+ QNetworkReplyImplPrivate *reply;
+
+private:
+ friend class QNetworkAccessManager;
+ friend class QNetworkAccessManagerPrivate;
+ friend class QNetworkReplyImplPrivate;
+
+ bool synchronous;
+};
+
+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 0000000000..fd16591c3e
--- /dev/null
+++ b/src/network/access/qnetworkaccesscache.cpp
@@ -0,0 +1,379 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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 0000000000..c2975009d8
--- /dev/null
+++ b/src/network/access/qnetworkaccesscache_p.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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;
+
+// this class is not about caching files but about
+// caching objects used by QNetworkAccessManager, e.g. existing TCP connections
+// or credentials.
+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 0000000000..13f4cd9cbb
--- /dev/null
+++ b/src/network/access/qnetworkaccesscachebackend.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#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()
+ , device(0)
+{
+}
+
+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);
+
+ // set the raw headers
+ QNetworkCacheMetaData::RawHeaderList rawHeaders = item.rawHeaders();
+ QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
+ end = rawHeaders.constEnd();
+ for ( ; it != end; ++it)
+ setRawHeader(it->first, it->second);
+
+ // handle a possible redirect
+ QVariant redirectionTarget = attributes.value(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirectionTarget.isValid()) {
+ setAttribute(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
+ redirectionRequested(redirectionTarget.toUrl());
+ }
+
+ // 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!");
+}
+
+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 0000000000..eda140ce43
--- /dev/null
+++ b/src/network/access/qnetworkaccesscachebackend_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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();
+
+ void upstreamReadyRead();
+ void downstreamReadyWrite();
+
+private:
+ bool sendCacheContents();
+ QIODevice *device;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKACCESSCACHEBACKEND_P_H
diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp
new file mode 100644
index 0000000000..ab60a72074
--- /dev/null
+++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp
@@ -0,0 +1,284 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkaccessdebugpipebackend_p.h"
+#include "QtCore/qdatastream.h"
+#include <QCoreApplication>
+#include "private/qnoncontiguousbytedevice_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#ifdef QT_BUILD_INTERNAL
+
+enum {
+ ReadBufferSize = 16384,
+ WriteBufferSize = ReadBufferSize
+};
+
+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()
+ : bareProtocol(false), hasUploadFinished(false), hasDownloadFinished(false),
+ hasEverythingFinished(false), bytesDownloaded(0), bytesUploaded(0)
+{
+}
+
+QNetworkAccessDebugPipeBackend::~QNetworkAccessDebugPipeBackend()
+{
+ // this is signals disconnect, not network!
+ 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);
+
+ // socket ready read -> we can push from socket to downstream
+ connect(&socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
+ connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError()));
+ connect(&socket, SIGNAL(disconnected()), SLOT(socketDisconnected()));
+ connect(&socket, SIGNAL(connected()), SLOT(socketConnected()));
+ // socket bytes written -> we can push more from upstream to socket
+ connect(&socket, SIGNAL(bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
+
+ bareProtocol = url().queryItemValue(QLatin1String("bare")) == QLatin1String("1");
+
+ if (operation() == QNetworkAccessManager::PutOperation) {
+ uploadByteDevice = createUploadByteDevice();
+ QObject::connect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot()));
+ QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection);
+ }
+}
+
+void QNetworkAccessDebugPipeBackend::socketReadyRead()
+{
+ pushFromSocketToDownstream();
+}
+
+void QNetworkAccessDebugPipeBackend::downstreamReadyWrite()
+{
+ pushFromSocketToDownstream();
+}
+
+void QNetworkAccessDebugPipeBackend::socketBytesWritten(qint64)
+{
+ pushFromUpstreamToSocket();
+}
+
+void QNetworkAccessDebugPipeBackend::uploadReadyReadSlot()
+{
+ pushFromUpstreamToSocket();
+}
+
+void QNetworkAccessDebugPipeBackend::pushFromSocketToDownstream()
+{
+ QByteArray buffer;
+
+ if (socket.state() == QAbstractSocket::ConnectingState) {
+ return;
+ }
+
+ forever {
+ if (hasDownloadFinished)
+ return;
+
+ buffer.resize(ReadBufferSize);
+ qint64 haveRead = socket.read(buffer.data(), ReadBufferSize);
+
+ if (haveRead == -1) {
+ hasDownloadFinished = true;
+ // this ensures a good last downloadProgress is emitted
+ setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
+ possiblyFinish();
+ break;
+ } else if (haveRead == 0) {
+ break;
+ } else {
+ // have read something
+ buffer.resize(haveRead);
+ bytesDownloaded += haveRead;
+
+ QByteDataBuffer list;
+ list.append(buffer);
+ buffer.clear(); // important because of implicit sharing!
+ writeDownstreamData(list);
+ }
+ }
+}
+
+void QNetworkAccessDebugPipeBackend::pushFromUpstreamToSocket()
+{
+ // FIXME
+ if (operation() == QNetworkAccessManager::PutOperation) {
+ if (hasUploadFinished)
+ return;
+
+ forever {
+ if (socket.bytesToWrite() >= WriteBufferSize)
+ return;
+
+ qint64 haveRead;
+ const char *readPointer = uploadByteDevice->readPointer(WriteBufferSize, haveRead);
+ if (haveRead == -1) {
+ // EOF
+ hasUploadFinished = true;
+ emitReplyUploadProgress(bytesUploaded, bytesUploaded);
+ possiblyFinish();
+ break;
+ } else if (haveRead == 0 || readPointer == 0) {
+ // nothing to read right now, we will be called again later
+ break;
+ } else {
+ qint64 haveWritten;
+ haveWritten = socket.write(readPointer, haveRead);
+
+ if (haveWritten < 0) {
+ // write error!
+ QString msg = QCoreApplication::translate("QNetworkAccessDebugPipeBackend", "Write error writing to %1: %2")
+ .arg(url().toString(), socket.errorString());
+ error(QNetworkReply::ProtocolFailure, msg);
+ finished();
+ return;
+ } else {
+ uploadByteDevice->advanceReadPointer(haveWritten);
+ bytesUploaded += haveWritten;
+ emitReplyUploadProgress(bytesUploaded, -1);
+ }
+
+ //QCoreApplication::processEvents();
+
+ }
+ }
+ }
+}
+
+void QNetworkAccessDebugPipeBackend::possiblyFinish()
+{
+ if (hasEverythingFinished)
+ return;
+ hasEverythingFinished = true;
+
+ if ((operation() == QNetworkAccessManager::GetOperation) && hasDownloadFinished) {
+ socket.close();
+ finished();
+ } else if ((operation() == QNetworkAccessManager::PutOperation) && hasUploadFinished) {
+ socket.close();
+ finished();
+ }
+
+
+}
+
+void QNetworkAccessDebugPipeBackend::closeDownstreamChannel()
+{
+ qWarning("QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d",operation());;
+ //if (operation() == QNetworkAccessManager::GetOperation)
+ // socket.disconnectFromHost();
+}
+
+
+void QNetworkAccessDebugPipeBackend::socketError()
+{
+ qWarning("QNetworkAccessDebugPipeBackend::socketError() %d",socket.error());
+ 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, QNetworkAccessDebugPipeBackend::tr("Socket error on %1: %2")
+ .arg(url().toString(), socket.errorString()));
+ finished();
+ disconnect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
+
+}
+
+void QNetworkAccessDebugPipeBackend::socketDisconnected()
+{
+ pushFromSocketToDownstream();
+
+ if (socket.bytesToWrite() == 0) {
+ // normal close
+ } else {
+ // abnormal close
+ QString msg = QNetworkAccessDebugPipeBackend::tr("Remote host closed the connection prematurely on %1")
+ .arg(url().toString());
+ error(QNetworkReply::RemoteHostClosedError, msg);
+ finished();
+ }
+}
+
+void QNetworkAccessDebugPipeBackend::socketConnected()
+{
+}
+
+
+#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 0000000000..c65857c3f8
--- /dev/null
+++ b/src/network/access/qnetworkaccessdebugpipebackend_p.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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:
+ QNetworkAccessDebugPipeBackend();
+ virtual ~QNetworkAccessDebugPipeBackend();
+
+ virtual void open();
+ virtual void closeDownstreamChannel();
+
+ virtual void downstreamReadyWrite();
+
+protected:
+ void pushFromSocketToDownstream();
+ void pushFromUpstreamToSocket();
+ void possiblyFinish();
+ QNonContiguousByteDevice *uploadByteDevice;
+
+private slots:
+ void uploadReadyReadSlot();
+ void socketReadyRead();
+ void socketBytesWritten(qint64 bytes);
+ void socketError();
+ void socketDisconnected();
+ void socketConnected();
+
+private:
+ QTcpSocket socket;
+ bool bareProtocol;
+ bool hasUploadFinished;
+ bool hasDownloadFinished;
+ bool hasEverythingFinished;
+
+ qint64 bytesDownloaded;
+ qint64 bytesUploaded;
+};
+
+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 0000000000..7ebf626b7d
--- /dev/null
+++ b/src/network/access/qnetworkaccessfilebackend.cpp
@@ -0,0 +1,276 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkaccessfilebackend_p.h"
+#include "qfileinfo.h"
+#include "qurlinfo.h"
+#include "qdir.h"
+#include "private/qnoncontiguousbytedevice_p.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().compare(QLatin1String("qrc"), Qt::CaseInsensitive) == 0 || url.isLocalFile()) {
+ return new QNetworkAccessFileBackend;
+ } else if (!url.scheme().isEmpty() && url.authority().isEmpty()) {
+ // check if QFile could, in theory, open this URL via the file engines
+ // it has to be in the format:
+ // prefix:path/to/file
+ // or prefix:/path/to/file
+ //
+ // this construct here must match the one below in open()
+ QFileInfo fi(url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery));
+ if ((url.scheme().length()==1) && fi.exists())
+ qWarning("QNetworkAccessFileBackendFactory: URL has no schema set, use file:// for files");
+ if (fi.exists() || (op == QNetworkAccessManager::PutOperation && fi.dir().exists()))
+ return new QNetworkAccessFileBackend;
+ }
+
+ return 0;
+}
+
+QNetworkAccessFileBackend::QNetworkAccessFileBackend()
+ : uploadByteDevice(0), totalBytes(0), hasUploadFinished(false)
+{
+}
+
+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 = QLatin1Char(':') + 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;
+ uploadByteDevice = createUploadByteDevice();
+ QObject::connect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot()));
+ QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection);
+ 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::uploadReadyReadSlot()
+{
+ if (hasUploadFinished)
+ return;
+
+ forever {
+ qint64 haveRead;
+ const char *readPointer = uploadByteDevice->readPointer(-1, haveRead);
+ if (haveRead == -1) {
+ // EOF
+ hasUploadFinished = true;
+ file.flush();
+ file.close();
+ finished();
+ break;
+ } else if (haveRead == 0 || readPointer == 0) {
+ // nothing to read right now, we will be called again later
+ break;
+ } else {
+ qint64 haveWritten;
+ haveWritten = file.write(readPointer, haveRead);
+
+ if (haveWritten < 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;
+ } else {
+ uploadByteDevice->advanceReadPointer(haveWritten);
+ }
+
+
+ file.flush();
+ }
+ }
+}
+
+void QNetworkAccessFileBackend::closeDownstreamChannel()
+{
+ if (operation() == QNetworkAccessManager::GetOperation) {
+ file.close();
+ }
+}
+
+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;
+
+ QByteDataBuffer list;
+ list.append(data);
+ data.clear(); // important because of implicit sharing!
+ writeDownstreamData(list);
+ }
+ 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 0000000000..c51d2934b3
--- /dev/null
+++ b/src/network/access/qnetworkaccessfilebackend_p.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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
+{
+ Q_OBJECT
+public:
+ QNetworkAccessFileBackend();
+ virtual ~QNetworkAccessFileBackend();
+
+ virtual void open();
+ virtual void closeDownstreamChannel();
+
+ virtual void downstreamReadyWrite();
+
+public slots:
+ void uploadReadyReadSlot();
+protected:
+ QNonContiguousByteDevice *uploadByteDevice;
+private:
+ QFile file;
+ qint64 totalBytes;
+ bool hasUploadFinished;
+
+ 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 0000000000..3ad1961e46
--- /dev/null
+++ b/src/network/access/qnetworkaccessftpbackend.cpp
@@ -0,0 +1,382 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkaccessftpbackend_p.h"
+#include "qnetworkaccessmanager_p.h"
+#include "QtNetwork/qauthenticator.h"
+#include "private/qnoncontiguousbytedevice_p.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().compare(QLatin1String("ftp"), Qt::CaseInsensitive) == 0)
+ return new QNetworkAccessFtpBackend;
+ return 0;
+}
+
+class QNetworkAccessCachedFtpConnection: public QFtp, public QNetworkAccessCache::CacheableObject
+{
+ // Q_OBJECT
+public:
+ QNetworkAccessCachedFtpConnection()
+ {
+ 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* objectCache = QNetworkAccessManagerPrivate::getObjectCache(this);
+ QByteArray cacheKey = makeCacheKey(url);
+ if (!objectCache->requestEntry(cacheKey, this,
+ SLOT(ftpConnectionReady(QNetworkAccessCache::CacheableObject*)))) {
+ ftp = new QNetworkAccessCachedFtpConnection;
+#ifndef QT_NO_BEARERMANAGEMENT
+ //copy network session down to the QFtp
+ ftp->setProperty("_q_networksession", property("_q_networksession"));
+#endif
+#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());
+
+ objectCache->addEntry(cacheKey, ftp);
+ ftpConnectionReady(ftp);
+ }
+
+ // Put operation
+ if (operation() == QNetworkAccessManager::PutOperation) {
+ uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
+ uploadDevice->setParent(this);
+ }
+}
+
+void QNetworkAccessFtpBackend::closeDownstreamChannel()
+{
+ state = Disconnecting;
+ if (operation() == QNetworkAccessManager::GetOperation)
+#ifndef Q_OS_WINCE
+ abort();
+#else
+ exit(3);
+#endif
+}
+
+void QNetworkAccessFtpBackend::downstreamReadyWrite()
+{
+ if (state == Transferring && ftp && ftp->bytesAvailable())
+ ftpReadyRead();
+}
+
+void QNetworkAccessFtpBackend::ftpConnectionReady(QNetworkAccessCache::CacheableObject *o)
+{
+ ftp = static_cast<QNetworkAccessCachedFtpConnection *>(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::getObjectCache(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::getObjectCache(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) {
+ ftp->rawCommand(QLatin1String("TYPE I"));
+ 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()
+{
+ QByteArray data = ftp->readAll();
+ QByteDataBuffer list;
+ list.append(data);
+ data.clear(); // important because of implicit sharing!
+ writeDownstreamData(list);
+}
+
+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 0000000000..ae5b16758d
--- /dev/null
+++ b/src/network/access/qnetworkaccessftpbackend_p.h
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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 QNetworkAccessCachedFtpConnection;
+
+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 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<QNetworkAccessCachedFtpConnection> ftp;
+ QIODevice *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 0000000000..c61911445b
--- /dev/null
+++ b/src/network/access/qnetworkaccesshttpbackend.cpp
@@ -0,0 +1,1191 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#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 "QtNetwork/private/qnetworksession_p.h"
+#include "qnetworkrequest_p.h"
+#include "qnetworkcookie_p.h"
+#include "QtCore/qdatetime.h"
+#include "QtCore/qelapsedtimer.h"
+#include "QtNetwork/qsslconfiguration.h"
+#include "qhttpthreaddelegate_p.h"
+#include "qthread.h"
+
+#ifndef QT_NO_HTTP
+
+#include <string.h> // for strchr
+
+Q_DECLARE_METATYPE(QSharedPointer<char>)
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkProxy;
+
+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 (uint(equal) < uint(comma)) {
+ // 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:
+ case QNetworkAccessManager::DeleteOperation:
+ case QNetworkAccessManager::CustomOperation:
+ 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;
+}
+
+QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
+ : QNetworkAccessBackend()
+ , statusCode(0)
+ , pendingDownloadDataEmissions(new QAtomicInt())
+ , pendingDownloadProgressEmissions(new QAtomicInt())
+ , loadingFromCache(false)
+ , usingZerocopyDownloadBuffer(false)
+#ifndef QT_NO_OPENSSL
+ , pendingSslConfiguration(0), pendingIgnoreAllSslErrors(false)
+#endif
+ , resumeOffset(0)
+{
+}
+
+QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
+{
+ // This will do nothing if the request was already finished or aborted
+ emit abortHttpRequest();
+
+#ifndef QT_NO_OPENSSL
+ delete pendingSslConfiguration;
+#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
+ */
+bool QNetworkAccessHttpBackend::loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest)
+{
+ QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
+ (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
+ if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
+ // If the request does not already specify preferred cache-control
+ // force reload from the network and tell any caching proxy servers to reload too
+ if (!request().rawHeaderList().contains("Cache-Control")) {
+ httpRequest.setHeaderField("Cache-Control", "no-cache");
+ httpRequest.setHeaderField("Pragma", "no-cache");
+ }
+ return false;
+ }
+
+ // The disk cache API does not currently support partial content retrieval.
+ // That is why we don't use the disk cache for any such requests.
+ if (request().hasRawHeader("Range"))
+ return false;
+
+ QAbstractNetworkCache *nc = networkCache();
+ if (!nc)
+ return false; // no local cache
+
+ QNetworkCacheMetaData metaData = nc->metaData(url());
+ if (!metaData.isValid())
+ return false; // not in cache
+
+ if (!metaData.saveToDisk())
+ return false;
+
+ 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));
+
+ if (CacheLoadControlAttribute == QNetworkRequest::PreferNetwork) {
+ it = cacheHeaders.findRawHeader("Cache-Control");
+ if (it != cacheHeaders.rawHeaders.constEnd()) {
+ QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
+ if (cacheControl.contains("must-revalidate"))
+ return false;
+ }
+ }
+
+ QDateTime currentDateTime = QDateTime::currentDateTime();
+ QDateTime expirationDate = metaData.expirationDate();
+
+#if 0
+ /*
+ * 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
+ */
+ int age_value = 0;
+ it = cacheHeaders.findRawHeader("age");
+ if (it != cacheHeaders.rawHeaders.constEnd())
+ age_value = it->second.toInt();
+
+ QDateTime dateHeader;
+ int date_value = 0;
+ it = cacheHeaders.findRawHeader("date");
+ if (it != cacheHeaders.rawHeaders.constEnd()) {
+ dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
+ date_value = dateHeader.toTime_t();
+ }
+
+ int now = currentDateTime.toUTC().toTime_t();
+ int request_time = now;
+ int response_time = now;
+
+ // Algorithm from RFC 2616 section 13.2.3
+ 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
+ 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");
+ }
+ }
+ }
+
+ // the cache-saving code below sets the expirationDate with date+max_age
+ // if "max-age" is present, or to Expires otherwise
+ int freshness_lifetime = dateHeader.secsTo(expirationDate);
+ bool response_is_fresh = (freshness_lifetime > current_age);
+#else
+ bool response_is_fresh = currentDateTime.secsTo(expirationDate) >= 0;
+#endif
+
+ if (!response_is_fresh)
+ return false;
+
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+ qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
+#endif
+ return sendCacheContents(metaData);
+}
+
+static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio)
+{
+ switch (prio) {
+ case QNetworkRequest::LowPriority:
+ return QHttpNetworkRequest::LowPriority;
+ case QNetworkRequest::HighPriority:
+ return QHttpNetworkRequest::HighPriority;
+ case QNetworkRequest::NormalPriority:
+ default:
+ return QHttpNetworkRequest::NormalPriority;
+ }
+}
+
+void QNetworkAccessHttpBackend::postRequest()
+{
+ QThread *thread = 0;
+ if (isSynchronous()) {
+ // A synchronous HTTP request uses its own thread
+ thread = new QThread();
+ QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
+ thread->start();
+ } else if (!manager->httpThread) {
+ // We use the manager-global thread.
+ // At some point we could switch to having multiple threads if it makes sense.
+ manager->httpThread = new QThread();
+ QObject::connect(manager->httpThread, SIGNAL(finished()), manager->httpThread, SLOT(deleteLater()));
+ manager->httpThread->start();
+#ifndef QT_NO_NETWORKPROXY
+ qRegisterMetaType<QNetworkProxy>("QNetworkProxy");
+#endif
+#ifndef QT_NO_OPENSSL
+ qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
+ qRegisterMetaType<QSslConfiguration>("QSslConfiguration");
+#endif
+ qRegisterMetaType<QList<QPair<QByteArray,QByteArray> > >("QList<QPair<QByteArray,QByteArray> >");
+ qRegisterMetaType<QHttpNetworkRequest>("QHttpNetworkRequest");
+ qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
+ qRegisterMetaType<QSharedPointer<char> >("QSharedPointer<char>");
+
+ thread = manager->httpThread;
+ } else {
+ // Asynchronous request, thread already exists
+ thread = manager->httpThread;
+ }
+
+ QUrl url = request().url();
+ httpRequest.setUrl(url);
+
+ bool ssl = url.scheme().toLower() == QLatin1String("https");
+ setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
+ httpRequest.setSsl(ssl);
+
+
+#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 (!ssl
+ && (p.capabilities() & QNetworkProxy::CachingCapability)
+ && (p.type() == QNetworkProxy::HttpProxy ||
+ p.type() == QNetworkProxy::HttpCachingProxy)) {
+ cacheProxy = p;
+ transparentProxy = QNetworkProxy::NoProxy;
+ break;
+ }
+ if (p.isTransparentProxy()) {
+ transparentProxy = p;
+ cacheProxy = QNetworkProxy::NoProxy;
+ break;
+ }
+ }
+
+ // check if at least one of the proxies
+ if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
+ cacheProxy.type() == QNetworkProxy::DefaultProxy) {
+ // unsuitable proxies
+ QMetaObject::invokeMethod(this, "error", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
+ Q_ARG(QString, tr("No suitable proxy found")));
+ QMetaObject::invokeMethod(this, "finished", isSynchronous() ? Qt::DirectConnection : Qt::QueuedConnection);
+ return;
+ }
+#endif
+
+
+ bool loadedFromCache = false;
+ httpRequest.setPriority(convert(request().priority()));
+
+ switch (operation()) {
+ case QNetworkAccessManager::GetOperation:
+ httpRequest.setOperation(QHttpNetworkRequest::Get);
+ loadedFromCache = loadFromCacheIfAllowed(httpRequest);
+ break;
+
+ case QNetworkAccessManager::HeadOperation:
+ httpRequest.setOperation(QHttpNetworkRequest::Head);
+ loadedFromCache = loadFromCacheIfAllowed(httpRequest);
+ break;
+
+ case QNetworkAccessManager::PostOperation:
+ invalidateCache();
+ httpRequest.setOperation(QHttpNetworkRequest::Post);
+ createUploadByteDevice();
+ break;
+
+ case QNetworkAccessManager::PutOperation:
+ invalidateCache();
+ httpRequest.setOperation(QHttpNetworkRequest::Put);
+ createUploadByteDevice();
+ break;
+
+ case QNetworkAccessManager::DeleteOperation:
+ invalidateCache();
+ httpRequest.setOperation(QHttpNetworkRequest::Delete);
+ break;
+
+ case QNetworkAccessManager::CustomOperation:
+ invalidateCache(); // for safety reasons, we don't know what the operation does
+ httpRequest.setOperation(QHttpNetworkRequest::Custom);
+ createUploadByteDevice();
+ httpRequest.setCustomVerb(request().attribute(
+ QNetworkRequest::CustomVerbAttribute).toByteArray());
+ break;
+
+ default:
+ break; // can't happen
+ }
+
+ if (loadedFromCache) {
+ // commented this out since it will be called later anyway
+ // by copyFinished()
+ //QNetworkAccessBackend::finished();
+ return; // no need to send the request! :)
+ }
+
+ QList<QByteArray> headers = request().rawHeaderList();
+ if (resumeOffset != 0) {
+ if (headers.contains("Range")) {
+ // Need to adjust resume offset for user specified range
+
+ headers.removeOne("Range");
+
+ // We've already verified that requestRange starts with "bytes=", see canResume.
+ QByteArray requestRange = request().rawHeader("Range").mid(6);
+
+ int index = requestRange.indexOf('-');
+
+ quint64 requestStartOffset = requestRange.left(index).toULongLong();
+ quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
+
+ requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
+ '-' + QByteArray::number(requestEndOffset);
+
+ httpRequest.setHeaderField("Range", requestRange);
+ } else {
+ httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
+ }
+ }
+
+ foreach (const QByteArray &header, headers)
+ httpRequest.setHeaderField(header, request().rawHeader(header));
+
+ if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
+ httpRequest.setPipeliningAllowed(true);
+
+ if (static_cast<QNetworkRequest::LoadControl>
+ (request().attribute(QNetworkRequest::AuthenticationReuseAttribute,
+ QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
+ httpRequest.setWithCredentials(false);
+
+
+ // Create the HTTP thread delegate
+ QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
+#ifndef Q_NO_BEARERMANAGEMENT
+ QVariant v(property("_q_networksession"));
+ if (v.isValid())
+ delegate->networkSession = qvariant_cast<QSharedPointer<QNetworkSession> >(v);
+#endif
+
+ // For the synchronous HTTP, this is the normal way the delegate gets deleted
+ // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished
+ connect(thread, SIGNAL(finished()), delegate, SLOT(deleteLater()));
+
+ // Set the properties it needs
+ delegate->httpRequest = httpRequest;
+#ifndef QT_NO_NETWORKPROXY
+ delegate->cacheProxy = cacheProxy;
+ delegate->transparentProxy = transparentProxy;
+#endif
+ delegate->ssl = ssl;
+#ifndef QT_NO_OPENSSL
+ if (ssl)
+ delegate->incomingSslConfiguration = request().sslConfiguration();
+#endif
+
+ // Do we use synchronous HTTP?
+ delegate->synchronous = isSynchronous();
+
+ // The authentication manager is used to avoid the BlockingQueuedConnection communication
+ // from HTTP thread to user thread in some cases.
+ delegate->authenticationManager = manager->authenticationManager;
+
+ if (!isSynchronous()) {
+ // Tell our zerocopy policy to the delegate
+ delegate->downloadBufferMaximumSize =
+ request().attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute).toLongLong();
+
+ // These atomic integers are used for signal compression
+ delegate->pendingDownloadData = pendingDownloadDataEmissions;
+ delegate->pendingDownloadProgress = pendingDownloadProgressEmissions;
+
+ // Connect the signals of the delegate to us
+ connect(delegate, SIGNAL(downloadData(QByteArray)),
+ this, SLOT(replyDownloadData(QByteArray)),
+ Qt::QueuedConnection);
+ connect(delegate, SIGNAL(downloadFinished()),
+ this, SLOT(replyFinished()),
+ Qt::QueuedConnection);
+ connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
+ this, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
+ Qt::QueuedConnection);
+ connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
+ this, SLOT(replyDownloadProgressSlot(qint64,qint64)),
+ Qt::QueuedConnection);
+ connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
+ this, SLOT(httpError(QNetworkReply::NetworkError, const QString)),
+ Qt::QueuedConnection);
+#ifndef QT_NO_OPENSSL
+ connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
+ this, SLOT(replySslConfigurationChanged(QSslConfiguration)),
+ Qt::QueuedConnection);
+#endif
+ // Those need to report back, therefire BlockingQueuedConnection
+ connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ this, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ Qt::BlockingQueuedConnection);
+#ifndef QT_NO_NETWORKPROXY
+ connect (delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ this, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ Qt::BlockingQueuedConnection);
+#endif
+#ifndef QT_NO_OPENSSL
+ connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
+ this, SLOT(replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *)),
+ Qt::BlockingQueuedConnection);
+#endif
+ // This signal we will use to start the request.
+ connect(this, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
+ connect(this, SIGNAL(abortHttpRequest()), delegate, SLOT(abortRequest()));
+
+ if (uploadByteDevice) {
+ QNonContiguousByteDeviceThreadForwardImpl *forwardUploadDevice =
+ new QNonContiguousByteDeviceThreadForwardImpl(uploadByteDevice->atEnd(), uploadByteDevice->size());
+ if (uploadByteDevice->isResetDisabled())
+ forwardUploadDevice->disableReset();
+ forwardUploadDevice->setParent(delegate); // needed to make sure it is moved on moveToThread()
+ delegate->httpRequest.setUploadByteDevice(forwardUploadDevice);
+
+ // From main thread to user thread:
+ QObject::connect(this, SIGNAL(haveUploadData(QByteArray, bool, qint64)),
+ forwardUploadDevice, SLOT(haveDataSlot(QByteArray, bool, qint64)), Qt::QueuedConnection);
+ QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
+ forwardUploadDevice, SIGNAL(readyRead()),
+ Qt::QueuedConnection);
+
+ // From http thread to user thread:
+ QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
+ this, SLOT(wantUploadDataSlot(qint64)));
+ QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
+ this, SLOT(sentUploadDataSlot(qint64)));
+ connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
+ this, SLOT(resetUploadDataSlot(bool*)),
+ Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
+ }
+ } else if (isSynchronous()) {
+ connect(this, SIGNAL(startHttpRequestSynchronously()), delegate, SLOT(startRequestSynchronously()), Qt::BlockingQueuedConnection);
+
+ if (uploadByteDevice) {
+ // For the synchronous HTTP use case the use thread (this one here) is blocked
+ // so we cannot use the asynchronous upload architecture.
+ // We therefore won't use the QNonContiguousByteDeviceThreadForwardImpl but directly
+ // use the uploadByteDevice provided to us by the QNetworkReplyImpl.
+ // The code that is in QNetworkReplyImplPrivate::setup() makes sure it is safe to use from a thread
+ // since it only wraps a QRingBuffer
+ delegate->httpRequest.setUploadByteDevice(uploadByteDevice.data());
+ }
+ }
+
+
+ // Move the delegate to the http thread
+ delegate->moveToThread(thread);
+ // This call automatically moves the uploadDevice too for the asynchronous case.
+
+ // Send an signal to the delegate so it starts working in the other thread
+ if (isSynchronous()) {
+ emit startHttpRequestSynchronously(); // This one is BlockingQueuedConnection, so it will return when all work is done
+
+ if (delegate->incomingErrorCode != QNetworkReply::NoError) {
+ replyDownloadMetaData
+ (delegate->incomingHeaders,
+ delegate->incomingStatusCode,
+ delegate->incomingReasonPhrase,
+ delegate->isPipeliningUsed,
+ QSharedPointer<char>(),
+ delegate->incomingContentLength);
+ httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
+ } else {
+ replyDownloadMetaData
+ (delegate->incomingHeaders,
+ delegate->incomingStatusCode,
+ delegate->incomingReasonPhrase,
+ delegate->isPipeliningUsed,
+ QSharedPointer<char>(),
+ delegate->incomingContentLength);
+ replyDownloadData(delegate->synchronousDownloadData);
+ }
+
+ // End the thread. It will delete itself from the finished() signal
+ thread->quit();
+
+ finished();
+ } else {
+ emit startHttpRequest(); // Signal to the HTTP thread and go back to user.
+ }
+}
+
+void QNetworkAccessHttpBackend::invalidateCache()
+{
+ QAbstractNetworkCache *nc = networkCache();
+ if (nc)
+ nc->remove(url());
+}
+
+void QNetworkAccessHttpBackend::open()
+{
+ postRequest();
+}
+
+void QNetworkAccessHttpBackend::closeDownstreamChannel()
+{
+ // FIXME Maybe we can get rid of this whole architecture part
+}
+
+void QNetworkAccessHttpBackend::downstreamReadyWrite()
+{
+ // FIXME Maybe we can get rid of this whole architecture part
+}
+
+void QNetworkAccessHttpBackend::setDownstreamLimited(bool b)
+{
+ Q_UNUSED(b);
+ // We know that readBuffer maximum size limiting is broken since quite a while.
+ // The task to fix this is QTBUG-15065
+}
+
+void QNetworkAccessHttpBackend::replyDownloadData(QByteArray d)
+{
+ int pendingSignals = (int)pendingDownloadDataEmissions->fetchAndAddAcquire(-1) - 1;
+
+ if (pendingSignals > 0) {
+ // Some more signal emissions to this slot are pending.
+ // Instead of writing the downstream data, we wait
+ // and do it in the next call we get
+ // (signal comppression)
+ pendingDownloadData.append(d);
+ return;
+ }
+
+ pendingDownloadData.append(d);
+ d.clear();
+ // We need to usa a copy for calling writeDownstreamData as we could
+ // possibly recurse into this this function when we call
+ // appendDownstreamDataSignalEmissions because the user might call
+ // processEvents() or spin an event loop when this occur.
+ QByteDataBuffer pendingDownloadDataCopy = pendingDownloadData;
+ pendingDownloadData.clear();
+ writeDownstreamData(pendingDownloadDataCopy);
+}
+
+void QNetworkAccessHttpBackend::replyFinished()
+{
+ // We are already loading from cache, we still however
+ // got this signal because it was posted already
+ if (loadingFromCache)
+ return;
+
+ 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::replyDownloadMetaData
+ (QList<QPair<QByteArray,QByteArray> > hm,
+ int sc,QString rp,bool pu,
+ QSharedPointer<char> db,
+ qint64 contentLength)
+{
+ statusCode = sc;
+ reasonPhrase = rp;
+
+ // Download buffer
+ if (!db.isNull()) {
+ reply->setDownloadBuffer(db, contentLength);
+ usingZerocopyDownloadBuffer = true;
+ } else {
+ usingZerocopyDownloadBuffer = false;
+ }
+
+ setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
+
+ // reconstruct the HTTP header
+ QList<QPair<QByteArray, QByteArray> > headerMap = hm;
+ 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()) {
+ if (qstricmp(it->first.constData(), "set-cookie") == 0)
+ value += '\n';
+ else
+ value += ", ";
+ }
+ value += it->second;
+ setRawHeader(it->first, value);
+ }
+
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
+
+ // is it a redirection?
+ 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::replyDownloadProgressSlot(qint64 received, qint64 total)
+{
+ // we can be sure here that there is a download buffer
+
+ int pendingSignals = (int)pendingDownloadProgressEmissions->fetchAndAddAcquire(-1) - 1;
+ if (pendingSignals > 0) {
+ // Let's ignore this signal and look at the next one coming in
+ // (signal comppression)
+ return;
+ }
+
+ // Now do the actual notification of new bytes
+ writeDownstreamDataDownloadBuffer(received, total);
+}
+
+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
+
+ error(errorCode, errorString);
+}
+
+#ifndef QT_NO_OPENSSL
+void QNetworkAccessHttpBackend::replySslErrors(
+ const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
+{
+ // Go to generic backend
+ sslErrors(list);
+ // Check if the callback set any ignore and return this here to http thread
+ if (pendingIgnoreAllSslErrors)
+ *ignoreAll = true;
+ if (!pendingIgnoreSslErrorsList.isEmpty())
+ *toBeIgnored = pendingIgnoreSslErrorsList;
+}
+
+void QNetworkAccessHttpBackend::replySslConfigurationChanged(const QSslConfiguration &c)
+{
+ // Receiving the used SSL configuration from the HTTP thread
+ if (pendingSslConfiguration)
+ *pendingSslConfiguration = c;
+ else if (!c.isNull())
+ pendingSslConfiguration = new QSslConfiguration(c);
+}
+#endif
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkAccessHttpBackend::resetUploadDataSlot(bool *r)
+{
+ *r = uploadByteDevice->reset();
+}
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkAccessHttpBackend::sentUploadDataSlot(qint64 amount)
+{
+ uploadByteDevice->advanceReadPointer(amount);
+}
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkAccessHttpBackend::wantUploadDataSlot(qint64 maxSize)
+{
+ // call readPointer
+ qint64 currentUploadDataLength = 0;
+ char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
+ // Let's make a copy of this data
+ QByteArray dataArray(data, currentUploadDataLength);
+
+ // Communicate back to HTTP thread
+ emit haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
+}
+
+/*
+ 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
+
+ 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);
+
+ checkForRedirect(status);
+
+ // This needs to be emitted in the event loop because it can be reached at
+ // the direct code path of qnam.get(...) before the user has a chance
+ // to connect any signals.
+ QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
+ qRegisterMetaType<QIODevice*>("QIODevice*");
+ QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents));
+
+
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+ qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
+#endif
+
+ // Set the following flag so we can ignore some signals from HTTP thread
+ // that would still come
+ loadingFromCache = true;
+ return true;
+}
+
+void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
+{
+ delete dev;
+ finished();
+}
+
+#ifndef QT_NO_OPENSSL
+void QNetworkAccessHttpBackend::ignoreSslErrors()
+{
+ pendingIgnoreAllSslErrors = true;
+}
+
+void QNetworkAccessHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
+{
+ // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
+ // is called before QNetworkAccessManager::get() (or post(), etc.)
+ pendingIgnoreSslErrorsList = errors;
+}
+
+void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
+{
+ if (pendingSslConfiguration)
+ config = *pendingSslConfiguration;
+ else
+ config = request().sslConfiguration();
+}
+
+void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig)
+{
+ // Setting a SSL configuration on a reply is not supported. The user needs to set
+ // her/his QSslConfiguration on the QNetworkRequest.
+ Q_UNUSED(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) {
+ QByteArray originalHeader = header;
+ 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;
+
+ // we are currently not using the date header to determine the expiration time of a page,
+ // but only the "Expires", "max-age" and "s-maxage" headers, see
+ // QNetworkAccessHttpBackend::validateCache() and below ("metaData.setExpirationDate()").
+ if (header == "date")
+ 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;
+ }
+
+ it = cacheHeaders.findRawHeader(header);
+ if (it != cacheHeaders.rawHeaders.constEnd()) {
+ // Match the behavior of Firefox and assume Cache-Control: "no-transform"
+ if (header == "content-encoding"
+ || header == "content-range"
+ || header == "content-type")
+ continue;
+
+ // For MS servers that send "Content-Length: 0" on 304 responses
+ // ignore this too
+ if (header == "content-length")
+ continue;
+ }
+
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+ QByteArray n = rawHeader(header);
+ QByteArray o;
+ 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(originalHeader, 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;
+ // only cache GET replies by default, all other replies (POST, PUT, DELETE)
+ // are not cacheable by default (according to RFC 2616 section 9)
+ if (httpRequest.operation() == QHttpNetworkRequest::Get) {
+
+ canDiskCache = true;
+ // 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;
+
+ // responses to POST might be cacheable
+ } else if (httpRequest.operation() == QHttpNetworkRequest::Post) {
+
+ canDiskCache = false;
+ // some pages contain "expires:" and "cache-control: no-cache" field,
+ // so we only might cache POST requests if we get "cache-control: max-age ..."
+ if (cacheControl.contains("max-age"))
+ canDiskCache = true;
+
+ // responses to PUT and DELETE are not cacheable
+ } else {
+ canDiskCache = false;
+ }
+
+ metaData.setSaveToDisk(canDiskCache);
+ QNetworkCacheMetaData::AttributesMap attributes;
+ if (statusCode != 304) {
+ // update the status code
+ attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
+ attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
+ } else {
+ // this is a redirection, keep the attributes intact
+ attributes = oldMetaData.attributes();
+ }
+ metaData.setAttributes(attributes);
+ return metaData;
+}
+
+bool QNetworkAccessHttpBackend::canResume() const
+{
+ // Only GET operation supports resuming.
+ if (operation() != QNetworkAccessManager::GetOperation)
+ return false;
+
+ // Can only resume if server/resource supports Range header.
+ QByteArray acceptRangesheaderName("Accept-Ranges");
+ if (!hasRawHeader(acceptRangesheaderName) || rawHeader(acceptRangesheaderName) == "none")
+ return false;
+
+ // We only support resuming for byte ranges.
+ if (request().hasRawHeader("Range")) {
+ QByteArray range = request().rawHeader("Range");
+ if (!range.startsWith("bytes="))
+ return false;
+ }
+
+ // If we're using a download buffer then we don't support resuming/migration
+ // right now. Too much trouble.
+ if (usingZerocopyDownloadBuffer)
+ return false;
+
+ return true;
+}
+
+void QNetworkAccessHttpBackend::setResumeOffset(quint64 offset)
+{
+ resumeOffset = offset;
+}
+
+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 0000000000..4778bd0c4e
--- /dev/null
+++ b/src/network/access/qnetworkaccesshttpbackend_p.h
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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"
+#include "QtCore/qsharedpointer.h"
+#include "qatomic.h"
+
+#ifndef QT_NO_HTTP
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkAccessCachedHttpConnection;
+
+class QNetworkAccessHttpBackendIODevice;
+
+class QNetworkAccessHttpBackend: public QNetworkAccessBackend
+{
+ Q_OBJECT
+public:
+ QNetworkAccessHttpBackend();
+ virtual ~QNetworkAccessHttpBackend();
+
+ virtual void open();
+ virtual void closeDownstreamChannel();
+
+ virtual void downstreamReadyWrite();
+ virtual void setDownstreamLimited(bool b);
+
+ virtual void copyFinished(QIODevice *);
+#ifndef QT_NO_OPENSSL
+ virtual void ignoreSslErrors();
+ virtual void ignoreSslErrors(const QList<QSslError> &errors);
+
+ virtual void fetchSslConfiguration(QSslConfiguration &configuration) const;
+ virtual void setSslConfiguration(const QSslConfiguration &configuration);
+#endif
+ QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const;
+
+ // we return true since HTTP needs to send PUT/POST data again after having authenticated
+ bool needsResetableUploadData() { return true; }
+
+ bool canResume() const;
+ void setResumeOffset(quint64 offset);
+
+signals:
+ // To HTTP thread:
+ void startHttpRequest();
+ void abortHttpRequest();
+
+ void startHttpRequestSynchronously();
+
+ void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
+private slots:
+ // From HTTP thread:
+ void replyDownloadData(QByteArray);
+ void replyFinished();
+ void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64);
+ void replyDownloadProgressSlot(qint64,qint64);
+ void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth);
+ void httpError(QNetworkReply::NetworkError error, const QString &errorString);
+#ifndef QT_NO_OPENSSL
+ void replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
+ void replySslConfigurationChanged(const QSslConfiguration&);
+#endif
+
+ // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
+ void resetUploadDataSlot(bool *r);
+ void wantUploadDataSlot(qint64);
+ void sentUploadDataSlot(qint64);
+
+ bool sendCacheContents(const QNetworkCacheMetaData &metaData);
+
+private:
+ QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread
+ int statusCode;
+ QString reasonPhrase;
+ // Will be increased by HTTP thread:
+ QSharedPointer<QAtomicInt> pendingDownloadDataEmissions;
+ QSharedPointer<QAtomicInt> pendingDownloadProgressEmissions;
+ bool loadingFromCache;
+ QByteDataBuffer pendingDownloadData;
+ bool usingZerocopyDownloadBuffer;
+
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration *pendingSslConfiguration;
+ bool pendingIgnoreAllSslErrors;
+ QList<QSslError> pendingIgnoreSslErrorsList;
+#endif
+
+ quint64 resumeOffset;
+
+ bool loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest);
+ void invalidateCache();
+ void postRequest();
+ void readFromHttp();
+ void checkForRedirect(const int statusCode);
+};
+
+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 0000000000..5a7521e33e
--- /dev/null
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -0,0 +1,1299 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkaccessmanager.h"
+#include "qnetworkaccessmanager_p.h"
+#include "qnetworkrequest.h"
+#include "qnetworkreply.h"
+#include "qnetworkreply_p.h"
+#include "qnetworkcookie.h"
+#include "qabstractnetworkcache.h"
+
+#include "QtNetwork/qnetworksession.h"
+#include "QtNetwork/private/qsharednetworksession_p.h"
+
+#include "qnetworkaccesshttpbackend_p.h"
+#include "qnetworkaccessftpbackend_p.h"
+#include "qnetworkaccessfilebackend_p.h"
+#include "qnetworkaccessdebugpipebackend_p.h"
+#include "qnetworkaccesscachebackend_p.h"
+#include "qnetworkreplydataimpl_p.h"
+#include "qnetworkreplyfileimpl_p.h"
+
+#include "QtCore/qbuffer.h"
+#include "QtCore/qurl.h"
+#include "QtCore/qvector.h"
+#include "QtNetwork/qauthenticator.h"
+#include "QtNetwork/qsslconfiguration.h"
+#include "QtNetwork/qnetworkconfigmanager.h"
+#include "QtNetwork/qhttpmultipart.h"
+#include "qhttpmultipart_p.h"
+
+#include "qthread.h"
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_HTTP
+Q_GLOBAL_STATIC(QNetworkAccessHttpBackendFactory, httpBackend)
+#endif // QT_NO_HTTP
+Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend)
+#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
+
+#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
+ send network requests and receive replies
+ \since 4.4
+
+ \ingroup network
+ \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. One QNetworkAccessManager
+ should be enough for the whole Qt application.
+
+ 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.
+
+ A simple download off the network could be accomplished with:
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 0
+
+ QNetworkAccessManager has an asynchronous API.
+ 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 After the request has finished, it is the responsibility of the user
+ to delete the QNetworkReply object at an appropriate time. Do not directly
+ delete it inside the slot connected to finished(). You can use the
+ deleteLater() function.
+
+ \note QNetworkAccessManager queues the requests it receives. The number
+ of requests executed in parallel is dependent on the protocol.
+ Currently, for the HTTP protocol on desktop platforms, 6 requests are
+ executed in parallel for one host/port combination.
+
+ A more involved example, assuming the manager is already existent,
+ can be:
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 1
+
+ \section1 Network and Roaming support
+
+ With the addition of the \l {Bearer Management} API to Qt 4.7
+ QNetworkAccessManager gained the ability to manage network connections.
+ QNetworkAccessManager can start the network interface if the device is
+ offline and terminates the interface if the current process is the last
+ one to use the uplink. Note that some platform utilize grace periods from
+ when the last application stops using a uplink until the system actually
+ terminates the connectivity link. Roaming is equally transparent. Any
+ queued/pending network requests are automatically transferred to new
+ access point.
+
+ Clients wanting to utilize this feature should not require any changes. In fact
+ it is likely that existing platform specific connection code can simply be
+ removed from the application.
+
+ \note The network and roaming support in QNetworkAccessManager is conditional
+ upon the platform supporting connection management. The
+ \l QNetworkConfigurationManager::NetworkSessionRequired can be used to
+ detect whether QNetworkAccessManager utilizes this feature. Currently only
+ Meego/Harmattan and Symbian platforms provide connection management support.
+
+ \note This feature cannot be used in combination with the Bearer Management
+ API as provided by QtMobility. Applications have to migrate to the Qt version
+ of Bearer Management.
+
+ \section1 Symbian Platform Security Requirements
+
+ On Symbian, processes which use this class must have the
+ \c NetworkServices platform security capability. If the client
+ process lacks this capability, operations will result in a panic.
+
+ Platform security capabilities are added via the
+ \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY}
+ qmake variable.
+
+ \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())
+
+ \value DeleteOperation delete contents operation (created with
+ deleteResource())
+
+ \value CustomOperation custom operation (created with
+ sendCustomRequest()) \since 4.7
+
+ \omitvalue UnknownOperation
+
+ \sa QNetworkReply::operation()
+*/
+
+/*!
+ \enum QNetworkAccessManager::NetworkAccessibility
+
+ Indicates whether the network is accessible via this network access manager.
+
+ \value UnknownAccessibility The network accessibility cannot be determined.
+ \value NotAccessible The network is not currently accessible, either because there
+ is currently no network coverage or network access has been
+ explicitly disabled by a call to setNetworkAccessible().
+ \value Accessible The network is accessible.
+
+ \sa networkAccessible
+*/
+
+/*!
+ \property QNetworkAccessManager::networkAccessible
+ \brief whether the network is currently accessible via this network access manager.
+
+ \since 4.7
+
+ If the network is \l {NotAccessible}{not accessible} the network access manager will not
+ process any new network requests, all such requests will fail with an error. Requests with
+ URLs with the file:// scheme will still be processed.
+
+ By default the value of this property reflects the physical state of the device. Applications
+ may override it to disable all network requests via this network access manager by calling
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 4
+
+ Network requests can be reenabled again by calling
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 5
+
+ \note Calling setNetworkAccessible() does not change the network state.
+*/
+
+/*!
+ \fn void QNetworkAccessManager::networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible)
+
+ This signal is emitted when the value of the \l networkAccessible property changes.
+ \a accessible is the new network accessibility.
+*/
+
+/*!
+ \fn void QNetworkAccessManager::networkSessionConnected()
+
+ \since 4.7
+
+ \internal
+
+ This signal is emitted when the status of the network session changes into a usable (Connected)
+ state. It is used to signal to QNetworkReplys to start or migrate their network operation once
+ the network session has been opened or finished roaming.
+*/
+
+/*!
+ \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.
+
+ \note Do not delete the \a reply object in the slot connected to this
+ signal. Use deleteLater().
+
+ \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()
+*/
+
+
+/*!
+ 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();
+
+ qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
+}
+
+/*!
+ 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
+
+ // Delete the QNetworkReply children first.
+ // Else a QAbstractNetworkCache might get deleted in ~QObject
+ // before a QNetworkReply that accesses the QAbstractNetworkCache
+ // object in its destructor.
+ qDeleteAll(findChildren<QNetworkReply *>());
+ // The other children will be deleted in this ~QObject
+ // FIXME instead of this "hack" make the QNetworkReplyImpl
+ // properly watch the cache deletion, e.g. via a QWeakPointer.
+}
+
+#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;
+ if (d->networkCache)
+ 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.
+
+ If \a cookieJar is in the same thread as this QNetworkAccessManager,
+ it will set the parent of the \a cookieJar
+ 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;
+ if (thread() == cookieJar->thread())
+ d->cookieJar->setParent(this);
+ }
+}
+
+/*!
+ Posts a request to obtain the network headers for \a request
+ and returns a new QNetworkReply object which will contain such headers.
+
+ The function is named after the HTTP request associated (HEAD).
+*/
+QNetworkReply *QNetworkAccessManager::head(const QNetworkRequest &request)
+{
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::HeadOperation, request));
+}
+
+/*!
+ Posts a request to obtain the contents of the target \a request
+ and returns a new QNetworkReply object opened for reading which emits the
+ \l{QIODevice::readyRead()}{readyRead()} signal whenever new data
+ arrives.
+
+ The contents as well as associated headers will be downloaded.
+
+ \sa post(), put(), deleteResource(), sendCustomRequest()
+*/
+QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
+{
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::GetOperation, request));
+}
+
+/*!
+ Sends an HTTP POST request to the destination specified by \a request
+ and returns a new QNetworkReply object opened for reading that will
+ contain the reply sent by the server. The contents of the \a data
+ device will be uploaded to the server.
+
+ \a data must be open for reading and must remain valid until the
+ finished() signal is emitted for this reply.
+
+ \note Sending a POST request on protocols other than HTTP and
+ HTTPS is undefined and will probably fail.
+
+ \sa get(), put(), deleteResource(), sendCustomRequest()
+*/
+QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QIODevice *data)
+{
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::PostOperation, request, data));
+}
+
+/*!
+ \overload
+
+ 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;
+}
+
+/*!
+ \since 4.8
+
+ \overload
+
+ Sends the contents of the \a multiPart message to the destination
+ specified by \a request.
+
+ This can be used for sending MIME multipart messages over HTTP.
+
+ \sa QHttpMultiPart, QHttpPart, put()
+*/
+QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
+ QIODevice *device = multiPart->d_func()->device;
+ QNetworkReply *reply = post(newRequest, device);
+ return reply;
+}
+
+/*!
+ \since 4.8
+
+ \overload
+
+ Sends the contents of the \a multiPart message to the destination
+ specified by \a request.
+
+ This can be used for sending MIME multipart messages over HTTP.
+
+ \sa QHttpMultiPart, QHttpPart, post()
+*/
+QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
+ QIODevice *device = multiPart->d_func()->device;
+ QNetworkReply *reply = put(newRequest, device);
+ return reply;
+}
+
+/*!
+ Uploads the contents of \a data to the destination \a request and
+ returnes a new QNetworkReply object that will be open for reply.
+
+ \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.
+
+ Whether anything will be available for reading from the returned
+ object 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.
+
+ \note 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(), deleteResource(), sendCustomRequest()
+*/
+QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QIODevice *data)
+{
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::PutOperation, request, data));
+}
+
+/*!
+ \overload
+
+ 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;
+}
+
+/*!
+ \since 4.6
+
+ Sends a request to delete the resource identified by the URL of \a request.
+
+ \note This feature is currently available for HTTP only, performing an
+ HTTP DELETE request.
+
+ \sa get(), post(), put(), sendCustomRequest()
+*/
+QNetworkReply *QNetworkAccessManager::deleteResource(const QNetworkRequest &request)
+{
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::DeleteOperation, request));
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+
+/*!
+ \since 4.7
+
+ Sets the network configuration that will be used when creating the
+ \l {QNetworkSession}{network session} to \a config.
+
+ The network configuration is used to create and open a network session before any request that
+ requires network access is process. If no network configuration is explicitly set via this
+ function the network configuration returned by
+ QNetworkConfigurationManager::defaultConfiguration() will be used.
+
+ To restore the default network configuration set the network configuration to the value
+ returned from QNetworkConfigurationManager::defaultConfiguration().
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 2
+
+ If an invalid network configuration is set, a network session will not be created. In this
+ case network requests will be processed regardless, but may fail. For example:
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 3
+
+ \sa configuration(), QNetworkSession
+*/
+void QNetworkAccessManager::setConfiguration(const QNetworkConfiguration &config)
+{
+ d_func()->createSession(config);
+}
+
+/*!
+ \since 4.7
+
+ Returns the network configuration that will be used to create the
+ \l {QNetworkSession}{network session} which will be used when processing network requests.
+
+ \sa setConfiguration(), activeConfiguration()
+*/
+QNetworkConfiguration QNetworkAccessManager::configuration() const
+{
+ Q_D(const QNetworkAccessManager);
+
+ if (d->networkSession)
+ return d->networkSession->configuration();
+ else
+ return QNetworkConfiguration();
+}
+
+/*!
+ \since 4.7
+
+ Returns the current active network configuration.
+
+ If the network configuration returned by configuration() is of type
+ QNetworkConfiguration::ServiceNetwork this function will return the current active child
+ network configuration of that configuration. Otherwise returns the same network configuration
+ as configuration().
+
+ Use this function to return the actual network configuration currently in use by the network
+ session.
+
+ \sa configuration()
+*/
+QNetworkConfiguration QNetworkAccessManager::activeConfiguration() const
+{
+ Q_D(const QNetworkAccessManager);
+
+ if (d->networkSession) {
+ QNetworkConfigurationManager manager;
+
+ return manager.configurationFromIdentifier(
+ d->networkSession->sessionProperty(QLatin1String("ActiveConfiguration")).toString());
+ } else {
+ return QNetworkConfiguration();
+ }
+}
+
+/*!
+ \since 4.7
+
+ Overrides the reported network accessibility. If \a accessible is NotAccessible the reported
+ network accessiblity will always be NotAccessible. Otherwise the reported network
+ accessibility will reflect the actual device state.
+*/
+void QNetworkAccessManager::setNetworkAccessible(QNetworkAccessManager::NetworkAccessibility accessible)
+{
+ Q_D(QNetworkAccessManager);
+
+ if (d->networkAccessible != accessible) {
+ NetworkAccessibility previous = networkAccessible();
+ d->networkAccessible = accessible;
+ NetworkAccessibility current = networkAccessible();
+ if (previous != current)
+ emit networkAccessibleChanged(current);
+ }
+}
+
+/*!
+ \since 4.7
+
+ Returns the current network accessibility.
+*/
+QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccessible() const
+{
+ Q_D(const QNetworkAccessManager);
+
+ if (d->networkSession) {
+ // d->online holds online/offline state of this network session.
+ if (d->online)
+ return d->networkAccessible;
+ else
+ return NotAccessible;
+ } else {
+ // Network accessibility is either disabled or unknown.
+ return (d->networkAccessible == NotAccessible) ? NotAccessible : UnknownAccessibility;
+ }
+}
+
+#endif // QT_NO_BEARERMANAGEMENT
+
+/*!
+ \since 4.7
+
+ Sends a custom request to the server identified by the URL of \a request.
+
+ It is the user's responsibility to send a \a verb to the server that is valid
+ according to the HTTP specification.
+
+ This method provides means to send verbs other than the common ones provided
+ via get() or post() etc., for instance sending an HTTP OPTIONS command.
+
+ If \a data is not empty, the contents of the \a data
+ device will be uploaded to the server; in that case, data must be open for
+ reading and must remain valid until the finished() signal is emitted for this reply.
+
+ \note This feature is currently available for HTTP only.
+
+ \sa get(), post(), put(), deleteResource()
+*/
+QNetworkReply *QNetworkAccessManager::sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data)
+{
+ QNetworkRequest newRequest(request);
+ newRequest.setAttribute(QNetworkRequest::CustomVerbAttribute, verb);
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::CustomOperation, newRequest, data));
+}
+
+/*!
+ 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);
+
+ bool isLocalFile = req.url().isLocalFile();
+ QString scheme = req.url().scheme().toLower();
+
+ // fast path for GET on file:// URLs
+ // The QNetworkAccessFileBackend will right now only be used for PUT
+ if ((op == QNetworkAccessManager::GetOperation || op == QNetworkAccessManager::HeadOperation)
+ && (isLocalFile || scheme == QLatin1String("qrc"))) {
+ return new QNetworkReplyFileImpl(this, req, op);
+ }
+
+ if ((op == QNetworkAccessManager::GetOperation || op == QNetworkAccessManager::HeadOperation)
+ && scheme == QLatin1String("data")) {
+ return new QNetworkReplyDataImpl(this, req, op);
+ }
+
+ // A request with QNetworkRequest::AlwaysCache does not need any bearer management
+ QNetworkRequest::CacheLoadControl mode =
+ static_cast<QNetworkRequest::CacheLoadControl>(
+ req.attribute(QNetworkRequest::CacheLoadControlAttribute,
+ QNetworkRequest::PreferNetwork).toInt());
+ if (mode == QNetworkRequest::AlwaysCache
+ && (op == QNetworkAccessManager::GetOperation
+ || op == QNetworkAccessManager::HeadOperation)) {
+ // FIXME Implement a QNetworkReplyCacheImpl instead, see QTBUG-15106
+ QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
+ QNetworkReplyImplPrivate *priv = reply->d_func();
+ priv->manager = this;
+ priv->backend = new QNetworkAccessCacheBackend();
+ priv->backend->manager = this->d_func();
+ priv->backend->setParent(reply);
+ priv->backend->reply = priv;
+ priv->setup(op, req, outgoingData);
+ return reply;
+ }
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ // Return a disabled network reply if network access is disabled.
+ // Except if the scheme is empty or file://.
+ if (!d->networkAccessible && !isLocalFile) {
+ return new QDisabledNetworkReply(this, req, op);
+ }
+
+ if (!d->networkSession && (d->initializeSession || !d->networkConfiguration.isEmpty())) {
+ QNetworkConfigurationManager manager;
+ if (!d->networkConfiguration.isEmpty()) {
+ d->createSession(manager.configurationFromIdentifier(d->networkConfiguration));
+ } else {
+ if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired)
+ d->createSession(manager.defaultConfiguration());
+ else
+ d->initializeSession = false;
+ }
+ }
+
+ if (d->networkSession)
+ d->networkSession->setSessionProperty(QLatin1String("AutoCloseSessionTimeout"), -1);
+#endif
+
+ 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 (static_cast<QNetworkRequest::LoadControl>
+ (request.attribute(QNetworkRequest::CookieLoadControlAttribute,
+ QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic) {
+ if (d->cookieJar) {
+ QList<QNetworkCookie> cookies = d->cookieJar->cookiesForUrl(request.url());
+ if (!cookies.isEmpty())
+ request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookies));
+ }
+ }
+
+ // first step: create the reply
+ QUrl url = request.url();
+ QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
+#ifndef QT_NO_BEARERMANAGEMENT
+ if (!isLocalFile) {
+ connect(this, SIGNAL(networkSessionConnected()),
+ reply, SLOT(_q_networkSessionConnected()));
+ }
+#endif
+ QNetworkReplyImplPrivate *priv = reply->d_func();
+ priv->manager = this;
+
+ // second step: fetch cached credentials
+ // This is not done for the time being, we should use signal emissions to request
+ // the credentials from cache.
+
+ // third step: find a backend
+ priv->backend = d->findBackend(op, request);
+
+#ifndef QT_NO_NETWORKPROXY
+ QList<QNetworkProxy> proxyList = d->queryProxy(QNetworkProxyQuery(request.url()));
+ priv->proxyList = proxyList;
+#endif
+ if (priv->backend) {
+ priv->backend->setParent(reply);
+ priv->backend->reply = priv;
+ }
+
+#ifndef QT_NO_OPENSSL
+ reply->setSslConfiguration(request.sslConfiguration());
+#endif
+
+ // fourth step: setup the reply
+ priv->setup(op, request, outgoingData);
+
+ return reply;
+}
+
+void QNetworkAccessManagerPrivate::_q_replyFinished()
+{
+ Q_Q(QNetworkAccessManager);
+
+ QNetworkReply *reply = qobject_cast<QNetworkReply *>(q->sender());
+ if (reply)
+ emit q->finished(reply);
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ if (networkSession && q->findChildren<QNetworkReply *>().count() == 1)
+ networkSession->setSessionProperty(QLatin1String("AutoCloseSessionTimeout"), 120000);
+#endif
+}
+
+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->cookieJar = new QNetworkCookieJar(that->q_func());
+ that->cookieJarCreated = true;
+ }
+}
+
+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
+ // also called when last URL is empty, e.g. on first call
+ if (backend->reply->urlForLastAuthentication.isEmpty()
+ || url != backend->reply->urlForLastAuthentication) {
+ QNetworkAuthenticationCredential cred = authenticationManager->fetchCachedCredentials(url, authenticator);
+ if (!cred.isNull()) {
+ authenticator->setUser(cred.user);
+ authenticator->setPassword(cred.password);
+ backend->reply->urlForLastAuthentication = url;
+ return;
+ }
+ }
+
+ // if we emit a signal here in synchronous mode, the user might spin
+ // an event loop, which might recurse and lead to problems
+ if (backend->isSynchronous())
+ return;
+
+ backend->reply->urlForLastAuthentication = url;
+ emit q->authenticationRequired(backend->reply->q_func(), authenticator);
+ authenticationManager->cacheCredentials(url, authenticator);
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBackend *backend,
+ const QNetworkProxy &proxy,
+ QAuthenticator *authenticator)
+{
+ Q_Q(QNetworkAccessManager);
+ // ### FIXME Tracking of successful authentications
+ // This code is a bit broken right now for SOCKS authentication
+ // first request: proxyAuthenticationRequired gets emitted, credentials gets saved
+ // second request: (proxy != backend->reply->lastProxyAuthentication) does not evaluate to true,
+ // proxyAuthenticationRequired gets emitted again
+ // possible solution: some tracking inside the authenticator
+ // or a new function proxyAuthenticationSucceeded(true|false)
+ if (proxy != backend->reply->lastProxyAuthentication) {
+ QNetworkAuthenticationCredential cred = authenticationManager->fetchCachedProxyCredentials(proxy);
+ if (!cred.isNull()) {
+ authenticator->setUser(cred.user);
+ authenticator->setPassword(cred.password);
+ return;
+ }
+ }
+
+ // if we emit a signal here in synchronous mode, the user might spin
+ // an event loop, which might recurse and lead to problems
+ if (backend->isSynchronous())
+ return;
+
+ backend->reply->lastProxyAuthentication = proxy;
+ emit q->proxyAuthenticationRequired(proxy, authenticator);
+ authenticationManager->cacheProxyCredentials(proxy, authenticator);
+}
+
+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::clearCache(QNetworkAccessManager *manager)
+{
+ manager->d_func()->objectCache.clear();
+ manager->d_func()->authenticationManager->clearCache();
+
+ if (manager->d_func()->httpThread) {
+ // The thread will deleteLater() itself from its finished() signal
+ manager->d_func()->httpThread->quit();
+ manager->d_func()->httpThread = 0;
+ }
+}
+
+QNetworkAccessManagerPrivate::~QNetworkAccessManagerPrivate()
+{
+ if (httpThread) {
+ // The thread will deleteLater() itself from its finished() signal
+ httpThread->quit();
+ httpThread = 0;
+ }
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+void QNetworkAccessManagerPrivate::createSession(const QNetworkConfiguration &config)
+{
+ Q_Q(QNetworkAccessManager);
+
+ initializeSession = false;
+
+ QSharedPointer<QNetworkSession> newSession;
+ if (config.isValid())
+ newSession = QSharedNetworkSessionManager::getSession(config);
+
+ if (networkSession) {
+ //do nothing if new and old session are the same
+ if (networkSession == newSession)
+ return;
+ //disconnect from old session
+ QObject::disconnect(networkSession.data(), SIGNAL(opened()), q, SIGNAL(networkSessionConnected()));
+ QObject::disconnect(networkSession.data(), SIGNAL(closed()), q, SLOT(_q_networkSessionClosed()));
+ QObject::disconnect(networkSession.data(), SIGNAL(stateChanged(QNetworkSession::State)),
+ q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)));
+ }
+
+ //switch to new session (null if config was invalid)
+ networkSession = newSession;
+
+ if (!networkSession) {
+ online = false;
+
+ if (networkAccessible == QNetworkAccessManager::NotAccessible)
+ emit q->networkAccessibleChanged(QNetworkAccessManager::NotAccessible);
+ else
+ emit q->networkAccessibleChanged(QNetworkAccessManager::UnknownAccessibility);
+
+ return;
+ }
+
+ //connect to new session
+ QObject::connect(networkSession.data(), SIGNAL(opened()), q, SIGNAL(networkSessionConnected()), Qt::QueuedConnection);
+ //QueuedConnection is used to avoid deleting the networkSession inside its closed signal
+ QObject::connect(networkSession.data(), SIGNAL(closed()), q, SLOT(_q_networkSessionClosed()), Qt::QueuedConnection);
+ QObject::connect(networkSession.data(), SIGNAL(stateChanged(QNetworkSession::State)),
+ q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)), Qt::QueuedConnection);
+
+ _q_networkSessionStateChanged(networkSession->state());
+}
+
+void QNetworkAccessManagerPrivate::_q_networkSessionClosed()
+{
+ Q_Q(QNetworkAccessManager);
+ if (networkSession) {
+ networkConfiguration = networkSession->configuration().identifier();
+
+ //disconnect from old session
+ QObject::disconnect(networkSession.data(), SIGNAL(opened()), q, SIGNAL(networkSessionConnected()));
+ QObject::disconnect(networkSession.data(), SIGNAL(closed()), q, SLOT(_q_networkSessionClosed()));
+ QObject::disconnect(networkSession.data(), SIGNAL(stateChanged(QNetworkSession::State)),
+ q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)));
+ networkSession.clear();
+ }
+}
+
+void QNetworkAccessManagerPrivate::_q_networkSessionStateChanged(QNetworkSession::State state)
+{
+ Q_Q(QNetworkAccessManager);
+
+ //Do not emit the networkSessionConnected signal here, except for roaming -> connected
+ //transition, otherwise it is emitted twice in a row when opening a connection.
+ if (state == QNetworkSession::Connected && lastSessionState == QNetworkSession::Roaming)
+ emit q->networkSessionConnected();
+ lastSessionState = state;
+
+ if (online) {
+ if (state != QNetworkSession::Connected && state != QNetworkSession::Roaming) {
+ online = false;
+ emit q->networkAccessibleChanged(QNetworkAccessManager::NotAccessible);
+ }
+ } else {
+ if (state == QNetworkSession::Connected || state == QNetworkSession::Roaming) {
+ online = true;
+ emit q->networkAccessibleChanged(networkAccessible);
+ }
+ }
+}
+#endif // QT_NO_BEARERMANAGEMENT
+
+QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+ // copy the request, we probably need to add some headers
+ QNetworkRequest newRequest(request);
+
+ // add Content-Type header if not there already
+ if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
+ QByteArray contentType;
+ contentType.reserve(34 + multiPart->d_func()->boundary.count());
+ contentType += "multipart/";
+ switch (multiPart->d_func()->contentType) {
+ case QHttpMultiPart::RelatedType:
+ contentType += "related";
+ break;
+ case QHttpMultiPart::FormDataType:
+ contentType += "form-data";
+ break;
+ case QHttpMultiPart::AlternativeType:
+ contentType += "alternative";
+ break;
+ default:
+ contentType += "mixed";
+ break;
+ }
+ // putting the boundary into quotes, recommended in RFC 2046 section 5.1.1
+ contentType += "; boundary=\"" + multiPart->d_func()->boundary + "\"";
+ newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
+ }
+
+ // add MIME-Version header if not there already (we must include the header
+ // if the message conforms to RFC 2045, see section 4 of that RFC)
+ QByteArray mimeHeader("MIME-Version");
+ if (!request.hasRawHeader(mimeHeader))
+ newRequest.setRawHeader(mimeHeader, QByteArray("1.0"));
+
+ QIODevice *device = multiPart->d_func()->device;
+ if (!device->isReadable()) {
+ if (!device->isOpen()) {
+ if (!device->open(QIODevice::ReadOnly))
+ qWarning("could not open device for reading");
+ } else {
+ qWarning("device is not readable");
+ }
+ }
+
+ return newRequest;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qnetworkaccessmanager.cpp"
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
new file mode 100644
index 0000000000..47760b210b
--- /dev/null
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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;
+#if !defined(QT_NO_BEARERMANAGEMENT) && !defined(QT_MOBILITY_BEARER)
+class QNetworkConfiguration;
+#endif
+class QHttpMultiPart;
+
+class QNetworkReplyImplPrivate;
+class QNetworkAccessManagerPrivate;
+class Q_NETWORK_EXPORT QNetworkAccessManager: public QObject
+{
+ Q_OBJECT
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ Q_PROPERTY(NetworkAccessibility networkAccessible READ networkAccessible WRITE setNetworkAccessible NOTIFY networkAccessibleChanged)
+#endif
+
+public:
+ enum Operation {
+ HeadOperation = 1,
+ GetOperation,
+ PutOperation,
+ PostOperation,
+ DeleteOperation,
+ CustomOperation,
+
+ UnknownOperation = 0
+ };
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ enum NetworkAccessibility {
+ UnknownAccessibility = -1,
+ NotAccessible = 0,
+ Accessible = 1
+ };
+#endif
+
+ 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 *post(const QNetworkRequest &request, QHttpMultiPart *multiPart);
+ QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
+ QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data);
+ QNetworkReply *put(const QNetworkRequest &request, QHttpMultiPart *multiPart);
+ QNetworkReply *deleteResource(const QNetworkRequest &request);
+ QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = 0);
+
+#if !defined(QT_NO_BEARERMANAGEMENT) && !defined(QT_MOBILITY_BEARER)
+ void setConfiguration(const QNetworkConfiguration &config);
+ QNetworkConfiguration configuration() const;
+ QNetworkConfiguration activeConfiguration() const;
+#endif
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ void setNetworkAccessible(NetworkAccessibility accessible);
+ NetworkAccessibility networkAccessible() const;
+#endif
+
+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
+
+#if !defined(QT_NO_BEARERMANAGEMENT) && !defined(QT_MOBILITY_BEARER)
+ void networkSessionConnected();
+#endif
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ void networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible);
+#endif
+
+protected:
+ virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
+ QIODevice *outgoingData = 0);
+
+private:
+ friend class QNetworkReplyImplPrivate;
+ friend class QNetworkAccessHttpBackend;
+
+ Q_DECLARE_PRIVATE(QNetworkAccessManager)
+ Q_PRIVATE_SLOT(d_func(), void _q_replyFinished())
+ Q_PRIVATE_SLOT(d_func(), void _q_replySslErrors(QList<QSslError>))
+#if !defined(QT_NO_BEARERMANAGEMENT) && !defined(QT_MOBILITY_BEARER)
+ Q_PRIVATE_SLOT(d_func(), void _q_networkSessionClosed())
+ Q_PRIVATE_SLOT(d_func(), void _q_networkSessionStateChanged(QNetworkSession::State))
+#endif
+};
+
+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 0000000000..f64cc4dc79
--- /dev/null
+++ b/src/network/access/qnetworkaccessmanager_p.h
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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"
+#include "QtNetwork/qnetworksession.h"
+#include "qnetworkaccessauthenticationmanager_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAuthenticator;
+class QAbstractNetworkCache;
+class QNetworkAuthenticationCredential;
+class QNetworkCookieJar;
+
+class QNetworkAccessManagerPrivate: public QObjectPrivate
+{
+public:
+ QNetworkAccessManagerPrivate()
+ : networkCache(0), cookieJar(0),
+ httpThread(0),
+#ifndef QT_NO_NETWORKPROXY
+ proxyFactory(0),
+#endif
+#ifndef QT_NO_BEARERMANAGEMENT
+ networkSession(0),
+ lastSessionState(QNetworkSession::Invalid),
+ networkAccessible(QNetworkAccessManager::Accessible),
+ online(false),
+ initializeSession(true),
+#endif
+ cookieJarCreated(false),
+ authenticationManager(new QNetworkAccessAuthenticationManager)
+ { }
+ ~QNetworkAccessManagerPrivate();
+
+ void _q_replyFinished();
+ void _q_replySslErrors(const QList<QSslError> &errors);
+ QNetworkReply *postProcess(QNetworkReply *reply);
+ void createCookieJar() const;
+
+ void authenticationRequired(QNetworkAccessBackend *backend, QAuthenticator *authenticator);
+ void cacheCredentials(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 cacheProxyCredentials(const QNetworkProxy &proxy, const QAuthenticator *auth);
+ QNetworkAuthenticationCredential *fetchCachedProxyCredentials(const QNetworkProxy &proxy,
+ const QAuthenticator *auth = 0);
+ QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query);
+#endif
+
+ QNetworkAccessBackend *findBackend(QNetworkAccessManager::Operation op, const QNetworkRequest &request);
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ void createSession(const QNetworkConfiguration &config);
+
+ void _q_networkSessionClosed();
+ void _q_networkSessionNewConfigurationActivated();
+ void _q_networkSessionPreferredConfigurationChanged(const QNetworkConfiguration &config,
+ bool isSeamless);
+ void _q_networkSessionStateChanged(QNetworkSession::State state);
+#endif
+
+ QNetworkRequest prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart);
+
+ // this is the cache for storing downloaded files
+ QAbstractNetworkCache *networkCache;
+
+ QNetworkCookieJar *cookieJar;
+
+ QThread *httpThread;
+
+
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy proxy;
+ QNetworkProxyFactory *proxyFactory;
+#endif
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ QSharedPointer<QNetworkSession> networkSession;
+ QNetworkSession::State lastSessionState;
+ QString networkConfiguration;
+ QNetworkAccessManager::NetworkAccessibility networkAccessible;
+ bool online;
+ bool initializeSession;
+#endif
+
+ bool cookieJarCreated;
+
+ // The cache with authorization data:
+ QSharedPointer<QNetworkAccessAuthenticationManager> authenticationManager;
+
+ // this cache can be used by individual backends to cache e.g. their TCP connections to a server
+ // and use the connections for multiple requests.
+ QNetworkAccessCache objectCache;
+ static inline QNetworkAccessCache *getObjectCache(QNetworkAccessBackend *backend)
+ { return &backend->manager->objectCache; }
+ 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 0000000000..52eb3453b8
--- /dev/null
+++ b/src/network/access/qnetworkcookie.cpp
@@ -0,0 +1,1052 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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/qstringlist.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.toUTC() == other.d->expirationDate.toUTC() &&
+ 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, bool isNameValue)
+{
+ // 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
+
+ // If its NAME=VALUE, retain the value as is
+ // refer to ttp://bugreports.qt.nokia.com/browse/QTBUG-17746
+ if (isNameValue)
+ second += '"';
+ ++i;
+ while (i < length) {
+ register char c = text.at(i);
+ if (c == '"') {
+ // end of quoted text
+ if (isNameValue)
+ second += '"';
+ break;
+ } else if (c == '\\') {
+ if (isNameValue)
+ second += '\\';
+ ++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('"')) &&
+ (!d->value.startsWith('"') &&
+ !d->value.endsWith('"'))) {
+ 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=";
+ QString domainNoDot = d->domain;
+ if (domainNoDot.startsWith(QLatin1Char('.'))) {
+ result += '.';
+ domainNoDot = domainNoDot.mid(1);
+ }
+ result += QUrl::toAce(domainNoDot);
+ }
+ if (!d->path.isEmpty()) {
+ result += "; path=";
+ result += QUrl::toPercentEncoding(d->path, "/");
+ }
+ }
+ return result;
+}
+
+static const char zones[] =
+ "pst\0" // -8
+ "pdt\0"
+ "mst\0" // -7
+ "mdt\0"
+ "cst\0" // -6
+ "cdt\0"
+ "est\0" // -5
+ "edt\0"
+ "ast\0" // -4
+ "nst\0" // -3
+ "gmt\0" // 0
+ "utc\0"
+ "bst\0"
+ "met\0" // 1
+ "eet\0" // 2
+ "jst\0" // 9
+ "\0";
+static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
+
+static const char months[] =
+ "jan\0"
+ "feb\0"
+ "mar\0"
+ "apr\0"
+ "may\0"
+ "jun\0"
+ "jul\0"
+ "aug\0"
+ "sep\0"
+ "oct\0"
+ "nov\0"
+ "dec\0"
+ "\0";
+
+static inline bool isNumber(char s)
+{ return s >= '0' && s <= '9'; }
+
+static inline bool isTerminator(char c)
+{ return c == '\n' || c == '\r'; }
+
+static inline bool isValueSeparator(char c)
+{ return isTerminator(c) || c == ';'; }
+
+static inline bool isWhitespace(char c)
+{ return c == ' ' || c == '\t'; }
+
+static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
+{
+ if (dateString[at] < 'a' || dateString[at] > 'z')
+ return false;
+ if (val == -1 && dateString.length() >= at + 3) {
+ int j = 0;
+ int i = 0;
+ while (i <= size) {
+ const char *str = array + i;
+ if (str[0] == dateString[at]
+ && str[1] == dateString[at + 1]
+ && str[2] == dateString[at + 2]) {
+ val = j;
+ return true;
+ }
+ i += strlen(str) + 1;
+ ++j;
+ }
+ }
+ return false;
+}
+
+//#define PARSEDATESTRINGDEBUG
+
+#define ADAY 1
+#define AMONTH 2
+#define AYEAR 4
+
+/*
+ Parse all the date formats that Firefox can.
+
+ The official format is:
+ expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
+
+ But browsers have been supporting a very wide range of date
+ strings. To work on many sites we need to support more then
+ just the official date format.
+
+ For reference see Firefox's PR_ParseTimeStringToExplodedTime in
+ prtime.c. The Firefox date parser is coded in a very complex way
+ and is slightly over ~700 lines long. While this implementation
+ will be slightly slower for the non standard dates it is smaller,
+ more readable, and maintainable.
+
+ Or in their own words:
+ "} // else what the hell is this."
+*/
+static QDateTime parseDateString(const QByteArray &dateString)
+{
+ QTime time;
+ // placeholders for values when we are not sure it is a year, month or day
+ int unknown[3] = {-1, -1, -1};
+ int month = -1;
+ int day = -1;
+ int year = -1;
+ int zoneOffset = -1;
+
+ // hour:minute:second.ms pm
+ QRegExp timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)"));
+
+ int at = 0;
+ while (at < dateString.length()) {
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << dateString.mid(at);
+#endif
+ bool isNum = isNumber(dateString[at]);
+
+ // Month
+ if (!isNum
+ && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
+ ++month;
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Month:" << month;
+#endif
+ at += 3;
+ continue;
+ }
+ // Zone
+ if (!isNum
+ && zoneOffset == -1
+ && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) {
+ int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1;
+ zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60;
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Zone:" << month;
+#endif
+ at += 3;
+ continue;
+ }
+ // Zone offset
+ if (!isNum
+ && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
+ && (dateString[at] == '+' || dateString[at] == '-')
+ && (at == 0
+ || isWhitespace(dateString[at - 1])
+ || dateString[at - 1] == ','
+ || (at >= 3
+ && (dateString[at - 3] == 'g')
+ && (dateString[at - 2] == 'm')
+ && (dateString[at - 1] == 't')))) {
+
+ int end = 1;
+ while (end < 5 && dateString.length() > at+end
+ && dateString[at + end] >= '0' && dateString[at + end] <= '9')
+ ++end;
+ int minutes = 0;
+ int hours = 0;
+ switch (end - 1) {
+ case 4:
+ minutes = atoi(dateString.mid(at + 3, 2).constData());
+ // fall through
+ case 2:
+ hours = atoi(dateString.mid(at + 1, 2).constData());
+ break;
+ case 1:
+ hours = atoi(dateString.mid(at + 1, 1).constData());
+ break;
+ default:
+ at += end;
+ continue;
+ }
+ if (end != 1) {
+ int sign = dateString[at] == '-' ? -1 : 1;
+ zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60));
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Zone offset:" << zoneOffset << hours << minutes;
+#endif
+ at += end;
+ continue;
+ }
+ }
+
+ // Time
+ if (isNum && time.isNull()
+ && dateString.length() >= at + 3
+ && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) {
+ // While the date can be found all over the string the format
+ // for the time is set and a nice regexp can be used.
+ int pos = timeRx.indexIn(QLatin1String(dateString), at);
+ if (pos != -1) {
+ QStringList list = timeRx.capturedTexts();
+ int h = atoi(list.at(1).toLatin1().constData());
+ int m = atoi(list.at(2).toLatin1().constData());
+ int s = atoi(list.at(4).toLatin1().constData());
+ int ms = atoi(list.at(6).toLatin1().constData());
+ if (h < 12 && !list.at(9).isEmpty())
+ if (list.at(9) == QLatin1String("pm"))
+ h += 12;
+ time = QTime(h, m, s, ms);
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Time:" << list << timeRx.matchedLength();
+#endif
+ at += timeRx.matchedLength();
+ continue;
+ }
+ }
+
+ // 4 digit Year
+ if (isNum
+ && year == -1
+ && dateString.length() > at + 3) {
+ if (isNumber(dateString[at + 1])
+ && isNumber(dateString[at + 2])
+ && isNumber(dateString[at + 3])) {
+ year = atoi(dateString.mid(at, 4).constData());
+ at += 4;
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Year:" << year;
+#endif
+ continue;
+ }
+ }
+
+ // a one or two digit number
+ // Could be month, day or year
+ if (isNum) {
+ int length = 1;
+ if (dateString.length() > at + 1
+ && isNumber(dateString[at + 1]))
+ ++length;
+ int x = atoi(dateString.mid(at, length).constData());
+ if (year == -1 && (x > 31 || x == 0)) {
+ year = x;
+ } else {
+ if (unknown[0] == -1) unknown[0] = x;
+ else if (unknown[1] == -1) unknown[1] = x;
+ else if (unknown[2] == -1) unknown[2] = x;
+ }
+ at += length;
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Saving" << x;
+#endif
+ continue;
+ }
+
+ // Unknown character, typically a weekday such as 'Mon'
+ ++at;
+ }
+
+ // Once we are done parsing the string take the digits in unknown
+ // and determine which is the unknown year/month/day
+
+ int couldBe[3] = { 0, 0, 0 };
+ int unknownCount = 3;
+ for (int i = 0; i < unknownCount; ++i) {
+ if (unknown[i] == -1) {
+ couldBe[i] = ADAY | AYEAR | AMONTH;
+ unknownCount = i;
+ continue;
+ }
+
+ if (unknown[i] >= 1)
+ couldBe[i] = ADAY;
+
+ if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
+ couldBe[i] |= AMONTH;
+
+ if (year == -1)
+ couldBe[i] |= AYEAR;
+ }
+
+ // For any possible day make sure one of the values that could be a month
+ // can contain that day.
+ // For any possible month make sure one of the values that can be a
+ // day that month can have.
+ // Example: 31 11 06
+ // 31 can't be a day because 11 and 6 don't have 31 days
+ for (int i = 0; i < unknownCount; ++i) {
+ int currentValue = unknown[i];
+ bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29;
+ bool findMatchingDay = couldBe[i] & AMONTH;
+ if (!findMatchingMonth || !findMatchingDay)
+ continue;
+ for (int j = 0; j < 3; ++j) {
+ if (j == i)
+ continue;
+ for (int k = 0; k < 2; ++k) {
+ if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
+ continue;
+ else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
+ continue;
+ int m = currentValue;
+ int d = unknown[j];
+ if (k == 0)
+ qSwap(m, d);
+ if (m == -1) m = month;
+ bool found = true;
+ switch(m) {
+ case 2:
+ // When we get 29 and the year ends up having only 28
+ // See date.isValid below
+ // Example: 29 23 Feb
+ if (d <= 29)
+ found = false;
+ break;
+ case 4: case 6: case 9: case 11:
+ if (d <= 30)
+ found = false;
+ break;
+ default:
+ if (d > 0 && d <= 31)
+ found = false;
+ }
+ if (k == 0) findMatchingMonth = found;
+ else if (k == 1) findMatchingDay = found;
+ }
+ }
+ if (findMatchingMonth)
+ couldBe[i] &= ~ADAY;
+ if (findMatchingDay)
+ couldBe[i] &= ~AMONTH;
+ }
+
+ // First set the year/month/day that have been deduced
+ // and reduce the set as we go along to deduce more
+ for (int i = 0; i < unknownCount; ++i) {
+ int unset = 0;
+ for (int j = 0; j < 3; ++j) {
+ if (couldBe[j] == ADAY && day == -1) {
+ day = unknown[j];
+ unset |= ADAY;
+ } else if (couldBe[j] == AMONTH && month == -1) {
+ month = unknown[j];
+ unset |= AMONTH;
+ } else if (couldBe[j] == AYEAR && year == -1) {
+ year = unknown[j];
+ unset |= AYEAR;
+ } else {
+ // common case
+ break;
+ }
+ couldBe[j] &= ~unset;
+ }
+ }
+
+ // Now fallback to a standardized order to fill in the rest with
+ for (int i = 0; i < unknownCount; ++i) {
+ if (couldBe[i] & AMONTH && month == -1) month = unknown[i];
+ else if (couldBe[i] & ADAY && day == -1) day = unknown[i];
+ else if (couldBe[i] & AYEAR && year == -1) year = unknown[i];
+ }
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Final set" << year << month << day;
+#endif
+
+ if (year == -1 || month == -1 || day == -1) {
+#ifdef PARSEDATESTRINGDEBUG
+ qDebug() << "Parser failure" << year << month << day;
+#endif
+ return QDateTime();
+ }
+
+ // Y2k behavior
+ int y2k = 0;
+ if (year < 70)
+ y2k = 2000;
+ else if (year < 100)
+ y2k = 1900;
+
+ QDate date(year + y2k, month, day);
+
+ // When we were given a bad cookie that when parsed
+ // set the day to 29 and the year to one that doesn't
+ // have the 29th of Feb rather then adding the extra
+ // complicated checking earlier just swap here.
+ // Example: 29 23 Feb
+ if (!date.isValid())
+ date = QDate(day + y2k, month, year);
+
+ QDateTime dateTime(date, time, Qt::UTC);
+
+ if (zoneOffset != -1) {
+ dateTime = dateTime.addSecs(zoneOffset);
+ }
+ if (!dateTime.isValid())
+ return QDateTime();
+ return dateTime;
+}
+
+/*!
+ 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)
+{
+ // cookieString can be a number of set-cookie header strings joined together
+ // by \n, parse each line separately.
+ QList<QNetworkCookie> cookies;
+ QList<QByteArray> list = cookieString.split('\n');
+ for (int a = 0; a < list.size(); a++)
+ cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a));
+ return cookies;
+}
+
+QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(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, true);
+ if (field.first.isEmpty() || field.second.isNull())
+ // parsing error
+ break;
+ 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, false);
+ field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive
+
+ if (field.first == "expires") {
+ position -= field.second.length();
+ int end;
+ for (end = position; end < length; ++end)
+ if (isValueSeparator(cookieString.at(end)))
+ break;
+
+ QByteArray dateString = cookieString.mid(position, end - position).trimmed();
+ position = end;
+ QDateTime dt = parseDateString(dateString.toLower());
+ if (!dt.isValid()) {
+ return result;
+ }
+ 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)));
+ if (normalizedDomain.isEmpty() && !rawDomain.isEmpty())
+ return result;
+ cookie.setDomain(maybeLeadingDot + normalizedDomain);
+ } else if (field.first == "max-age") {
+ bool ok = false;
+ int secs = field.second.toInt(&ok);
+ if (!ok)
+ return result;
+ 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
+ return result;
+ }
+ } else {
+ // got an unknown field in the cookie
+ // what do we do?
+ }
+
+ position = nextNonWhitespace(cookieString, position);
+ }
+ }
+
+ if (!cookie.name().isEmpty())
+ 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
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkcookie.h b/src/network/access/qnetworkcookie.h
new file mode 100644
index 0000000000..6060b1a896
--- /dev/null
+++ b/src/network/access/qnetworkcookie.h
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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);
+
+#ifndef QT_NO_DEBUG_STREAM
+class QDebug;
+Q_NETWORK_EXPORT QDebug operator<<(QDebug, const QNetworkCookie &);
+#endif
+
+QT_END_NAMESPACE
+
+// ### Qt5 remove this include
+#include <QtNetwork/QNetworkCookieJar>
+
+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 0000000000..0d6dd70c03
--- /dev/null
+++ b/src/network/access/qnetworkcookie_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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) { }
+ static QList<QNetworkCookie> parseSetCookieHeaderLine(const QByteArray &cookieString);
+
+ 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/qnetworkcookiejar.cpp b/src/network/access/qnetworkcookiejar.cpp
new file mode 100644
index 0000000000..53fab9f0c4
--- /dev/null
+++ b/src/network/access/qnetworkcookiejar.cpp
@@ -0,0 +1,346 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkcookiejar.h"
+#include "qnetworkcookiejartlds_p.h"
+#include "qnetworkcookiejar_p.h"
+
+#include "QtNetwork/qnetworkcookie.h"
+#include "QtCore/qurl.h"
+#include "QtCore/qdatetime.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \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 cookies are set for \a 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
+ if (cookie.path().isEmpty())
+ cookie.setPath(defaultPath);
+ // don't do path checking. See http://bugreports.qt.nokia.com/browse/QTBUG-5815
+// else if (!isParentPath(pathAndFileName, cookie.path())) {
+// continue; // not accepted
+// }
+ if (cookie.domain().isEmpty()) {
+ cookie.setDomain(defaultDomain);
+ } else {
+ // Ensure the domain starts with a dot if its field was not empty
+ // in the HTTP header. There are some servers that forget the
+ // leading dot and this is actually forbidden according to RFC 2109,
+ // but all browsers accept it anyway so we do that as well.
+ if (!cookie.domain().startsWith(QLatin1Char('.')))
+ cookie.setDomain(QLatin1Char('.') + cookie.domain());
+
+ QString domain = cookie.domain();
+ if (!(isParentDomain(domain, defaultDomain)
+ || isParentDomain(defaultDomain, domain)))
+ continue; // not accepted
+
+ // the check for effective TLDs makes the "embedded dot" rule from RFC 2109 section 4.3.2
+ // redundant; the "leading dot" rule has been relaxed anyway, see above
+ // we remove the leading dot for this check
+ if (QNetworkCookieJarPrivate::isEffectiveTLD(domain.remove(0, 1)))
+ 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 decreasingly 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;
+ bool isEncrypted = url.scheme().toLower() == QLatin1String("https");
+
+ // 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;
+ if ((*it).isSecure() && !isEncrypted)
+ 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;
+}
+
+bool QNetworkCookieJarPrivate::isEffectiveTLD(const QString &domain)
+{
+ // for domain 'foo.bar.com':
+ // 1. return if TLD table contains 'foo.bar.com'
+ if (containsTLDEntry(domain))
+ return true;
+
+ if (domain.contains(QLatin1Char('.'))) {
+ int count = domain.size() - domain.indexOf(QLatin1Char('.'));
+ QString wildCardDomain;
+ wildCardDomain.reserve(count + 1);
+ wildCardDomain.append(QLatin1Char('*'));
+ wildCardDomain.append(domain.right(count));
+ // 2. if table contains '*.bar.com',
+ // test if table contains '!foo.bar.com'
+ if (containsTLDEntry(wildCardDomain)) {
+ QString exceptionDomain;
+ exceptionDomain.reserve(domain.size() + 1);
+ exceptionDomain.append(QLatin1Char('!'));
+ exceptionDomain.append(domain);
+ return (! containsTLDEntry(exceptionDomain));
+ }
+ }
+ return false;
+}
+
+bool QNetworkCookieJarPrivate::containsTLDEntry(const QString &entry)
+{
+ int index = qHash(entry) % tldCount;
+ int currentDomainIndex = tldIndices[index];
+ while (currentDomainIndex < tldIndices[index+1]) {
+ QString currentEntry = QString::fromUtf8(tldData + currentDomainIndex);
+ if (currentEntry == entry)
+ return true;
+ currentDomainIndex += qstrlen(tldData + currentDomainIndex) + 1; // +1 for the ending \0
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkcookiejar.h b/src/network/access/qnetworkcookiejar.h
new file mode 100644
index 0000000000..46c0b9c9f7
--- /dev/null
+++ b/src/network/access/qnetworkcookiejar.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKCOOKIEJAR_H
+#define QNETWORKCOOKIEJAR_H
+
+#include <QtCore/QObject>
+#include <QtCore/QUrl>
+
+// ### Qt5 remove this include
+#include <QtNetwork/QNetworkCookie>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Network)
+
+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)
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif
diff --git a/src/network/access/qnetworkcookiejar_p.h b/src/network/access/qnetworkcookiejar_p.h
new file mode 100644
index 0000000000..d6dc45057c
--- /dev/null
+++ b/src/network/access/qnetworkcookiejar_p.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKCOOKIEJAR_P_H
+#define QNETWORKCOOKIEJAR_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"
+#include "qnetworkcookie.h"
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkCookieJarPrivate: public QObjectPrivate
+{
+public:
+ QList<QNetworkCookie> allCookies;
+
+ static bool Q_AUTOTEST_EXPORT isEffectiveTLD(const QString &domain);
+ static bool containsTLDEntry(const QString &entry);
+
+ Q_DECLARE_PUBLIC(QNetworkCookieJar)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qnetworkcookiejartlds_p.h b/src/network/access/qnetworkcookiejartlds_p.h
new file mode 100644
index 0000000000..b06d881131
--- /dev/null
+++ b/src/network/access/qnetworkcookiejartlds_p.h
@@ -0,0 +1,6481 @@
+// Version: MPL 1.1/GPL 2.0/LGPL 2.1
+//
+// The contents of this file are subject to the Mozilla Public License Version
+// 1.1 (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+// http://www.mozilla.org/MPL/
+//
+// Software distributed under the License is distributed on an "AS IS" basis,
+// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+// for the specific language governing rights and limitations under the
+// License.
+//
+// The Original Code is the Public Suffix List.
+//
+// The Initial Developer of the Original Code is
+// Jo Hermans <jo.hermans@gmail.com>.
+// Portions created by the Initial Developer are Copyright (C) 2007
+// the Initial Developer. All Rights Reserved.
+//
+// Contributor(s):
+// Ruben Arakelyan <ruben@wackomenace.co.uk>
+// Gervase Markham <gerv@gerv.net>
+// Pamela Greene <pamg.bugs@gmail.com>
+// David Triendl <david@triendl.name>
+// Jothan Frakes <jothan@gmail.com>
+// The kind representatives of many TLD registries
+//
+// Alternatively, the contents of this file may be used under the terms of
+// either the GNU General Public License Version 2 or later (the "GPL"), or
+// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+// in which case the provisions of the GPL or the LGPL are applicable instead
+// of those above. If you wish to allow use of your version of this file only
+// under the terms of either the GPL or the LGPL, and not to allow others to
+// use your version of this file under the terms of the MPL, indicate your
+// decision by deleting the provisions above and replace them with the notice
+// and other provisions required by the GPL or the LGPL. If you do not delete
+// the provisions above, a recipient may use your version of this file under
+// the terms of any one of the MPL, the GPL or the LGPL.
+//
+
+#ifndef QNETWORKCOOKIEJARTLD_P_H
+#define QNETWORKCOOKIEJARTLD_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.
+//
+
+QT_BEGIN_NAMESPACE
+
+// note to maintainer:
+// this file should be updated before each release ->
+// for instructions see the program at
+// util/network/cookiejar-generateTLDs
+
+static const quint16 tldCount = 3949;
+static const quint16 tldIndices[] = {
+0,
+7,
+14,
+14,
+20,
+51,
+61,
+93,
+100,
+100,
+116,
+159,
+167,
+180,
+180,
+193,
+234,
+234,
+234,
+255,
+255,
+255,
+280,
+280,
+287,
+287,
+295,
+303,
+313,
+326,
+326,
+380,
+393,
+413,
+419,
+419,
+419,
+424,
+438,
+438,
+469,
+515,
+515,
+515,
+534,
+534,
+557,
+557,
+557,
+557,
+572,
+572,
+572,
+579,
+587,
+597,
+612,
+612,
+624,
+636,
+648,
+662,
+687,
+709,
+714,
+740,
+766,
+789,
+789,
+805,
+805,
+810,
+815,
+815,
+824,
+824,
+831,
+857,
+869,
+891,
+891,
+916,
+916,
+916,
+927,
+934,
+964,
+971,
+987,
+987,
+987,
+1008,
+1008,
+1016,
+1016,
+1030,
+1030,
+1052,
+1075,
+1075,
+1082,
+1087,
+1115,
+1135,
+1135,
+1135,
+1172,
+1178,
+1178,
+1178,
+1202,
+1207,
+1220,
+1220,
+1266,
+1266,
+1266,
+1266,
+1272,
+1290,
+1316,
+1316,
+1332,
+1332,
+1339,
+1339,
+1352,
+1352,
+1389,
+1389,
+1408,
+1415,
+1437,
+1444,
+1489,
+1489,
+1502,
+1502,
+1512,
+1518,
+1539,
+1555,
+1562,
+1584,
+1598,
+1607,
+1607,
+1607,
+1632,
+1652,
+1652,
+1658,
+1658,
+1675,
+1682,
+1709,
+1733,
+1748,
+1776,
+1783,
+1783,
+1790,
+1797,
+1826,
+1850,
+1850,
+1856,
+1880,
+1887,
+1901,
+1921,
+1947,
+1961,
+1967,
+1967,
+1967,
+1972,
+1986,
+1986,
+1986,
+2009,
+2029,
+2029,
+2047,
+2061,
+2075,
+2075,
+2075,
+2075,
+2075,
+2075,
+2082,
+2082,
+2124,
+2124,
+2129,
+2162,
+2162,
+2162,
+2236,
+2256,
+2263,
+2276,
+2283,
+2313,
+2313,
+2347,
+2380,
+2387,
+2387,
+2387,
+2431,
+2438,
+2445,
+2452,
+2459,
+2459,
+2469,
+2490,
+2516,
+2527,
+2540,
+2540,
+2586,
+2610,
+2630,
+2630,
+2653,
+2660,
+2669,
+2693,
+2693,
+2710,
+2710,
+2719,
+2719,
+2734,
+2740,
+2740,
+2753,
+2753,
+2763,
+2770,
+2775,
+2782,
+2789,
+2802,
+2820,
+2827,
+2827,
+2841,
+2855,
+2855,
+2865,
+2872,
+2884,
+2884,
+2919,
+2937,
+2955,
+2962,
+3012,
+3042,
+3073,
+3083,
+3083,
+3100,
+3105,
+3112,
+3131,
+3131,
+3166,
+3180,
+3187,
+3194,
+3211,
+3218,
+3223,
+3233,
+3249,
+3259,
+3268,
+3314,
+3314,
+3324,
+3324,
+3336,
+3336,
+3336,
+3336,
+3350,
+3363,
+3376,
+3398,
+3416,
+3445,
+3464,
+3488,
+3488,
+3497,
+3545,
+3552,
+3552,
+3552,
+3566,
+3573,
+3573,
+3573,
+3581,
+3581,
+3603,
+3603,
+3615,
+3621,
+3621,
+3683,
+3683,
+3710,
+3710,
+3716,
+3716,
+3748,
+3770,
+3791,
+3803,
+3810,
+3817,
+3833,
+3846,
+3846,
+3852,
+3876,
+3876,
+3882,
+3903,
+3910,
+3939,
+3939,
+3939,
+3947,
+3962,
+3962,
+3981,
+3994,
+4021,
+4030,
+4042,
+4085,
+4085,
+4096,
+4096,
+4104,
+4123,
+4123,
+4123,
+4143,
+4154,
+4164,
+4194,
+4194,
+4194,
+4205,
+4205,
+4222,
+4238,
+4301,
+4309,
+4322,
+4331,
+4331,
+4331,
+4331,
+4331,
+4347,
+4365,
+4375,
+4375,
+4385,
+4398,
+4412,
+4430,
+4430,
+4437,
+4447,
+4463,
+4472,
+4472,
+4472,
+4484,
+4484,
+4484,
+4484,
+4484,
+4490,
+4490,
+4511,
+4511,
+4522,
+4522,
+4522,
+4522,
+4528,
+4528,
+4534,
+4551,
+4551,
+4551,
+4564,
+4583,
+4583,
+4611,
+4611,
+4611,
+4622,
+4622,
+4649,
+4668,
+4677,
+4692,
+4692,
+4692,
+4705,
+4723,
+4723,
+4723,
+4729,
+4729,
+4743,
+4743,
+4750,
+4750,
+4763,
+4770,
+4776,
+4776,
+4776,
+4793,
+4811,
+4811,
+4811,
+4821,
+4821,
+4841,
+4857,
+4891,
+4897,
+4903,
+4903,
+4903,
+4919,
+4935,
+4942,
+4958,
+4958,
+4975,
+4975,
+4975,
+4985,
+4985,
+5020,
+5032,
+5032,
+5040,
+5053,
+5068,
+5079,
+5079,
+5101,
+5115,
+5115,
+5135,
+5154,
+5161,
+5161,
+5168,
+5168,
+5184,
+5210,
+5210,
+5238,
+5255,
+5278,
+5285,
+5308,
+5318,
+5327,
+5327,
+5333,
+5333,
+5340,
+5348,
+5355,
+5366,
+5377,
+5384,
+5408,
+5415,
+5422,
+5435,
+5442,
+5482,
+5482,
+5482,
+5482,
+5498,
+5527,
+5534,
+5541,
+5572,
+5572,
+5572,
+5579,
+5591,
+5602,
+5602,
+5621,
+5621,
+5646,
+5664,
+5671,
+5671,
+5681,
+5696,
+5707,
+5716,
+5716,
+5723,
+5723,
+5732,
+5742,
+5742,
+5763,
+5770,
+5776,
+5790,
+5790,
+5797,
+5806,
+5816,
+5832,
+5882,
+5882,
+5882,
+5882,
+5893,
+5934,
+5934,
+5965,
+5965,
+5980,
+5980,
+5980,
+5980,
+6007,
+6017,
+6034,
+6051,
+6065,
+6075,
+6075,
+6091,
+6097,
+6097,
+6109,
+6109,
+6109,
+6122,
+6147,
+6168,
+6168,
+6191,
+6191,
+6191,
+6191,
+6191,
+6198,
+6217,
+6224,
+6224,
+6231,
+6245,
+6252,
+6252,
+6270,
+6270,
+6284,
+6305,
+6315,
+6322,
+6322,
+6322,
+6329,
+6329,
+6329,
+6353,
+6361,
+6361,
+6375,
+6391,
+6405,
+6405,
+6415,
+6431,
+6431,
+6431,
+6431,
+6458,
+6475,
+6475,
+6475,
+6482,
+6489,
+6496,
+6496,
+6503,
+6520,
+6520,
+6520,
+6527,
+6527,
+6544,
+6561,
+6573,
+6573,
+6580,
+6580,
+6587,
+6587,
+6592,
+6592,
+6592,
+6592,
+6599,
+6599,
+6612,
+6633,
+6649,
+6649,
+6669,
+6676,
+6683,
+6683,
+6713,
+6720,
+6727,
+6736,
+6736,
+6746,
+6770,
+6807,
+6814,
+6827,
+6846,
+6846,
+6864,
+6864,
+6864,
+6864,
+6881,
+6888,
+6888,
+6888,
+6914,
+6935,
+6935,
+6935,
+6953,
+6959,
+6966,
+6983,
+6983,
+6983,
+7013,
+7023,
+7023,
+7023,
+7023,
+7037,
+7044,
+7058,
+7079,
+7086,
+7123,
+7134,
+7155,
+7168,
+7178,
+7203,
+7227,
+7236,
+7258,
+7265,
+7274,
+7274,
+7303,
+7303,
+7314,
+7314,
+7345,
+7352,
+7367,
+7377,
+7388,
+7388,
+7402,
+7402,
+7409,
+7421,
+7421,
+7467,
+7484,
+7484,
+7484,
+7491,
+7532,
+7532,
+7539,
+7546,
+7546,
+7553,
+7573,
+7573,
+7573,
+7580,
+7587,
+7594,
+7594,
+7594,
+7606,
+7606,
+7637,
+7637,
+7637,
+7664,
+7664,
+7664,
+7677,
+7684,
+7701,
+7723,
+7723,
+7723,
+7723,
+7734,
+7734,
+7734,
+7748,
+7748,
+7748,
+7748,
+7759,
+7759,
+7775,
+7775,
+7782,
+7789,
+7817,
+7824,
+7831,
+7836,
+7865,
+7865,
+7876,
+7901,
+7901,
+7908,
+7918,
+7938,
+7945,
+7945,
+7957,
+7964,
+7979,
+7986,
+7994,
+8007,
+8007,
+8014,
+8021,
+8061,
+8061,
+8071,
+8088,
+8131,
+8138,
+8153,
+8160,
+8160,
+8160,
+8175,
+8183,
+8197,
+8211,
+8211,
+8211,
+8243,
+8243,
+8243,
+8243,
+8259,
+8259,
+8259,
+8259,
+8259,
+8266,
+8275,
+8281,
+8281,
+8281,
+8281,
+8288,
+8288,
+8309,
+8309,
+8309,
+8330,
+8330,
+8330,
+8330,
+8337,
+8343,
+8343,
+8360,
+8370,
+8370,
+8380,
+8380,
+8386,
+8386,
+8397,
+8415,
+8415,
+8428,
+8454,
+8460,
+8475,
+8492,
+8526,
+8554,
+8554,
+8583,
+8583,
+8583,
+8598,
+8607,
+8617,
+8617,
+8642,
+8652,
+8652,
+8652,
+8662,
+8688,
+8688,
+8704,
+8704,
+8747,
+8765,
+8775,
+8783,
+8811,
+8835,
+8835,
+8835,
+8850,
+8859,
+8884,
+8910,
+8919,
+8952,
+8978,
+8978,
+8991,
+8991,
+8991,
+8999,
+8999,
+8999,
+9030,
+9030,
+9030,
+9030,
+9030,
+9041,
+9048,
+9048,
+9054,
+9054,
+9054,
+9086,
+9108,
+9108,
+9119,
+9119,
+9130,
+9144,
+9152,
+9161,
+9174,
+9194,
+9207,
+9207,
+9207,
+9232,
+9242,
+9242,
+9271,
+9290,
+9308,
+9308,
+9308,
+9308,
+9320,
+9333,
+9343,
+9356,
+9379,
+9379,
+9379,
+9395,
+9395,
+9406,
+9419,
+9419,
+9419,
+9419,
+9450,
+9485,
+9485,
+9497,
+9497,
+9505,
+9517,
+9528,
+9528,
+9551,
+9564,
+9586,
+9586,
+9608,
+9608,
+9626,
+9626,
+9626,
+9653,
+9653,
+9681,
+9681,
+9681,
+9698,
+9698,
+9698,
+9714,
+9729,
+9737,
+9737,
+9765,
+9765,
+9765,
+9765,
+9765,
+9825,
+9844,
+9866,
+9880,
+9880,
+9880,
+9880,
+9886,
+9895,
+9895,
+9895,
+9895,
+9895,
+9913,
+9913,
+9924,
+9958,
+9958,
+9967,
+9975,
+9975,
+9975,
+9981,
+9998,
+9998,
+9998,
+10012,
+10036,
+10036,
+10066,
+10079,
+10097,
+10121,
+10133,
+10142,
+10142,
+10142,
+10156,
+10173,
+10173,
+10173,
+10196,
+10205,
+10205,
+10205,
+10205,
+10218,
+10234,
+10240,
+10240,
+10240,
+10264,
+10273,
+10286,
+10286,
+10286,
+10286,
+10299,
+10299,
+10309,
+10309,
+10339,
+10358,
+10358,
+10374,
+10374,
+10390,
+10390,
+10413,
+10413,
+10439,
+10461,
+10467,
+10467,
+10467,
+10492,
+10492,
+10501,
+10528,
+10528,
+10528,
+10539,
+10583,
+10583,
+10583,
+10613,
+10613,
+10619,
+10628,
+10645,
+10645,
+10645,
+10650,
+10671,
+10687,
+10709,
+10709,
+10709,
+10709,
+10709,
+10727,
+10727,
+10727,
+10727,
+10733,
+10733,
+10768,
+10768,
+10773,
+10780,
+10788,
+10788,
+10797,
+10797,
+10835,
+10835,
+10845,
+10852,
+10861,
+10861,
+10861,
+10861,
+10861,
+10861,
+10861,
+10875,
+10888,
+10907,
+10907,
+10907,
+10920,
+10920,
+10932,
+10946,
+10977,
+10977,
+10997,
+11008,
+11037,
+11059,
+11059,
+11059,
+11067,
+11067,
+11067,
+11067,
+11067,
+11077,
+11091,
+11102,
+11102,
+11112,
+11125,
+11129,
+11129,
+11154,
+11154,
+11154,
+11164,
+11164,
+11189,
+11189,
+11189,
+11189,
+11189,
+11196,
+11196,
+11227,
+11238,
+11247,
+11256,
+11265,
+11265,
+11286,
+11307,
+11330,
+11337,
+11378,
+11378,
+11389,
+11413,
+11454,
+11469,
+11475,
+11475,
+11475,
+11475,
+11503,
+11503,
+11503,
+11503,
+11534,
+11534,
+11550,
+11550,
+11557,
+11557,
+11557,
+11557,
+11574,
+11585,
+11585,
+11603,
+11626,
+11643,
+11643,
+11643,
+11643,
+11663,
+11663,
+11682,
+11696,
+11696,
+11696,
+11696,
+11706,
+11706,
+11706,
+11706,
+11723,
+11723,
+11757,
+11757,
+11773,
+11795,
+11813,
+11836,
+11836,
+11883,
+11903,
+11952,
+11965,
+11965,
+11984,
+11997,
+12008,
+12008,
+12008,
+12024,
+12043,
+12065,
+12071,
+12099,
+12129,
+12140,
+12140,
+12146,
+12161,
+12161,
+12161,
+12161,
+12167,
+12167,
+12180,
+12186,
+12208,
+12226,
+12243,
+12243,
+12252,
+12252,
+12274,
+12289,
+12302,
+12302,
+12313,
+12313,
+12313,
+12313,
+12358,
+12358,
+12358,
+12369,
+12375,
+12391,
+12391,
+12391,
+12391,
+12405,
+12410,
+12416,
+12416,
+12436,
+12436,
+12436,
+12443,
+12443,
+12462,
+12477,
+12492,
+12492,
+12503,
+12519,
+12525,
+12531,
+12571,
+12571,
+12591,
+12591,
+12601,
+12641,
+12641,
+12641,
+12657,
+12657,
+12657,
+12699,
+12699,
+12699,
+12712,
+12728,
+12744,
+12761,
+12769,
+12782,
+12793,
+12823,
+12836,
+12851,
+12863,
+12890,
+12900,
+12900,
+12900,
+12910,
+12910,
+12924,
+12924,
+12924,
+12924,
+12924,
+12952,
+12984,
+13003,
+13038,
+13038,
+13056,
+13056,
+13056,
+13056,
+13074,
+13081,
+13093,
+13103,
+13103,
+13112,
+13119,
+13132,
+13132,
+13132,
+13141,
+13151,
+13183,
+13193,
+13206,
+13206,
+13206,
+13236,
+13252,
+13267,
+13267,
+13294,
+13307,
+13307,
+13307,
+13343,
+13349,
+13349,
+13349,
+13375,
+13375,
+13375,
+13384,
+13384,
+13384,
+13393,
+13393,
+13393,
+13402,
+13414,
+13425,
+13445,
+13467,
+13485,
+13499,
+13509,
+13528,
+13528,
+13549,
+13549,
+13559,
+13570,
+13570,
+13570,
+13570,
+13598,
+13637,
+13647,
+13647,
+13661,
+13673,
+13682,
+13687,
+13694,
+13694,
+13720,
+13733,
+13742,
+13748,
+13771,
+13795,
+13795,
+13814,
+13821,
+13821,
+13855,
+13862,
+13862,
+13862,
+13874,
+13874,
+13897,
+13909,
+13909,
+13947,
+13947,
+13952,
+13952,
+13970,
+13979,
+13979,
+13979,
+14008,
+14049,
+14049,
+14049,
+14049,
+14049,
+14049,
+14060,
+14083,
+14083,
+14091,
+14101,
+14101,
+14101,
+14101,
+14118,
+14136,
+14195,
+14195,
+14195,
+14213,
+14213,
+14232,
+14232,
+14253,
+14253,
+14258,
+14275,
+14275,
+14304,
+14311,
+14311,
+14318,
+14318,
+14318,
+14318,
+14318,
+14318,
+14325,
+14325,
+14345,
+14345,
+14379,
+14389,
+14422,
+14422,
+14422,
+14422,
+14435,
+14441,
+14441,
+14460,
+14460,
+14471,
+14471,
+14481,
+14495,
+14495,
+14495,
+14502,
+14502,
+14502,
+14502,
+14524,
+14533,
+14541,
+14552,
+14552,
+14552,
+14552,
+14563,
+14563,
+14568,
+14568,
+14585,
+14595,
+14602,
+14628,
+14628,
+14645,
+14672,
+14678,
+14697,
+14697,
+14734,
+14757,
+14764,
+14771,
+14771,
+14771,
+14796,
+14815,
+14822,
+14845,
+14861,
+14861,
+14861,
+14873,
+14873,
+14873,
+14902,
+14920,
+14920,
+14926,
+14926,
+14926,
+14941,
+14949,
+14949,
+14949,
+14949,
+14949,
+14960,
+14960,
+14971,
+14971,
+14998,
+15003,
+15003,
+15003,
+15013,
+15013,
+15027,
+15027,
+15027,
+15043,
+15053,
+15063,
+15074,
+15083,
+15093,
+15093,
+15121,
+15121,
+15128,
+15128,
+15144,
+15144,
+15144,
+15144,
+15165,
+15170,
+15170,
+15170,
+15170,
+15170,
+15170,
+15170,
+15186,
+15206,
+15206,
+15206,
+15224,
+15236,
+15236,
+15252,
+15258,
+15258,
+15258,
+15258,
+15264,
+15277,
+15288,
+15307,
+15307,
+15318,
+15328,
+15328,
+15334,
+15334,
+15363,
+15399,
+15399,
+15422,
+15438,
+15447,
+15447,
+15456,
+15456,
+15456,
+15495,
+15495,
+15495,
+15495,
+15511,
+15511,
+15530,
+15557,
+15566,
+15582,
+15590,
+15590,
+15604,
+15604,
+15625,
+15635,
+15655,
+15655,
+15655,
+15655,
+15655,
+15665,
+15675,
+15675,
+15675,
+15675,
+15675,
+15682,
+15682,
+15682,
+15694,
+15719,
+15749,
+15749,
+15794,
+15794,
+15837,
+15854,
+15854,
+15861,
+15861,
+15861,
+15861,
+15884,
+15900,
+15911,
+15931,
+15931,
+15931,
+15931,
+15931,
+15963,
+15963,
+15996,
+16006,
+16013,
+16024,
+16034,
+16049,
+16049,
+16049,
+16059,
+16059,
+16059,
+16059,
+16059,
+16059,
+16059,
+16064,
+16075,
+16104,
+16104,
+16117,
+16124,
+16124,
+16124,
+16124,
+16130,
+16145,
+16159,
+16190,
+16193,
+16196,
+16210,
+16224,
+16243,
+16255,
+16261,
+16280,
+16283,
+16292,
+16295,
+16295,
+16301,
+16304,
+16322,
+16325,
+16328,
+16349,
+16355,
+16382,
+16408,
+16414,
+16414,
+16414,
+16458,
+16477,
+16480,
+16485,
+16488,
+16514,
+16520,
+16530,
+16530,
+16546,
+16562,
+16597,
+16603,
+16622,
+16622,
+16646,
+16669,
+16672,
+16715,
+16715,
+16715,
+16718,
+16718,
+16727,
+16733,
+16752,
+16770,
+16787,
+16794,
+16810,
+16827,
+16840,
+16850,
+16876,
+16901,
+16901,
+16901,
+16916,
+16919,
+16926,
+16943,
+16972,
+16978,
+16978,
+16978,
+16981,
+17008,
+17008,
+17016,
+17053,
+17053,
+17053,
+17053,
+17072,
+17086,
+17105,
+17139,
+17153,
+17162,
+17162,
+17177,
+17177,
+17177,
+17177,
+17195,
+17218,
+17221,
+17221,
+17237,
+17276,
+17276,
+17300,
+17319,
+17339,
+17357,
+17370,
+17383,
+17400,
+17407,
+17419,
+17439,
+17439,
+17449,
+17465,
+17475,
+17504,
+17527,
+17527,
+17534,
+17548,
+17564,
+17564,
+17598,
+17598,
+17601,
+17630,
+17649,
+17669,
+17669,
+17679,
+17686,
+17757,
+17776,
+17796,
+17810,
+17840,
+17840,
+17877,
+17884,
+17884,
+17911,
+17936,
+17970,
+17980,
+17995,
+17995,
+18008,
+18011,
+18036,
+18075,
+18097,
+18097,
+18104,
+18115,
+18115,
+18129,
+18134,
+18141,
+18141,
+18157,
+18180,
+18190,
+18217,
+18224,
+18252,
+18284,
+18284,
+18296,
+18299,
+18312,
+18322,
+18338,
+18348,
+18348,
+18363,
+18372,
+18387,
+18387,
+18390,
+18402,
+18445,
+18445,
+18445,
+18466,
+18479,
+18479,
+18498,
+18508,
+18511,
+18511,
+18511,
+18511,
+18511,
+18526,
+18558,
+18586,
+18622,
+18643,
+18670,
+18686,
+18710,
+18720,
+18739,
+18739,
+18742,
+18745,
+18771,
+18774,
+18777,
+18791,
+18813,
+18816,
+18822,
+18839,
+18845,
+18851,
+18854,
+18878,
+18881,
+18896,
+18896,
+18899,
+18926,
+18937,
+18940,
+18953,
+18963,
+19010,
+19010,
+19017,
+19046,
+19060,
+19060,
+19087,
+19095,
+19101,
+19101,
+19128,
+19146,
+19157,
+19157,
+19188,
+19198,
+19205,
+19223,
+19230,
+19255,
+19280,
+19280,
+19283,
+19292,
+19301,
+19320,
+19323,
+19323,
+19341,
+19341,
+19365,
+19378,
+19381,
+19394,
+19423,
+19433,
+19440,
+19466,
+19490,
+19490,
+19490,
+19497,
+19504,
+19511,
+19511,
+19518,
+19545,
+19568,
+19575,
+19575,
+19583,
+19586,
+19592,
+19592,
+19609,
+19622,
+19629,
+19641,
+19641,
+19656,
+19673,
+19700,
+19723,
+19733,
+19746,
+19759,
+19769,
+19782,
+19789,
+19795,
+19831,
+19834,
+19841,
+19851,
+19854,
+19880,
+19895,
+19898,
+19898,
+19911,
+19922,
+19950,
+20020,
+20030,
+20059,
+20062,
+20089,
+20107,
+20151,
+20154,
+20175,
+20205,
+20208,
+20229,
+20229,
+20255,
+20261,
+20261,
+20283,
+20335,
+20362,
+20385,
+20392,
+20392,
+20392,
+20392,
+20418,
+20418,
+20418,
+20418,
+20443,
+20446,
+20446,
+20452,
+20452,
+20467,
+20467,
+20489,
+20489,
+20498,
+20525,
+20554,
+20560,
+20575,
+20589,
+20589,
+20589,
+20614,
+20652,
+20659,
+20659,
+20668,
+20679,
+20679,
+20679,
+20685,
+20685,
+20685,
+20685,
+20685,
+20685,
+20696,
+20733,
+20760,
+20766,
+20769,
+20792,
+20792,
+20792,
+20792,
+20813,
+20813,
+20813,
+20813,
+20826,
+20826,
+20846,
+20862,
+20880,
+20887,
+20901,
+20908,
+20933,
+20938,
+20958,
+20969,
+20978,
+20978,
+20978,
+20985,
+20985,
+20985,
+21000,
+21010,
+21010,
+21014,
+21026,
+21033,
+21041,
+21041,
+21051,
+21051,
+21064,
+21071,
+21113,
+21120,
+21120,
+21127,
+21134,
+21142,
+21164,
+21164,
+21164,
+21188,
+21195,
+21208,
+21215,
+21226,
+21226,
+21226,
+21238,
+21249,
+21255,
+21255,
+21265,
+21279,
+21296,
+21301,
+21315,
+21321,
+21331,
+21331,
+21331,
+21337,
+21343,
+21343,
+21351,
+21351,
+21361,
+21368,
+21383,
+21383,
+21389,
+21413,
+21437,
+21449,
+21462,
+21478,
+21506,
+21534,
+21546,
+21546,
+21557,
+21580,
+21595,
+21595,
+21595,
+21595,
+21626,
+21626,
+21646,
+21670,
+21676,
+21688,
+21688,
+21716,
+21731,
+21762,
+21762,
+21762,
+21762,
+21762,
+21783,
+21789,
+21799,
+21828,
+21828,
+21837,
+21845,
+21868,
+21874,
+21874,
+21880,
+21880,
+21889,
+21901,
+21910,
+21941,
+21941,
+21959,
+21959,
+21959,
+21966,
+21966,
+21972,
+21972,
+21995,
+22011,
+22043,
+22043,
+22051,
+22051,
+22060,
+22060,
+22060,
+22074,
+22086,
+22086,
+22099,
+22099,
+22120,
+22120,
+22134,
+22144,
+22150,
+22158,
+22164,
+22164,
+22171,
+22199,
+22210,
+22210,
+22210,
+22220,
+22228,
+22228,
+22239,
+22261,
+22304,
+22304,
+22312,
+22349,
+22349,
+22349,
+22357,
+22381,
+22381,
+22390,
+22390,
+22390,
+22390,
+22402,
+22413,
+22413,
+22422,
+22422,
+22445,
+22445,
+22445,
+22456,
+22456,
+22469,
+22479,
+22501,
+22512,
+22528,
+22528,
+22528,
+22528,
+22528,
+22528,
+22528,
+22540,
+22540,
+22546,
+22546,
+22546,
+22546,
+22569,
+22591,
+22591,
+22591,
+22591,
+22610,
+22655,
+22655,
+22667,
+22667,
+22677,
+22677,
+22692,
+22692,
+22702,
+22702,
+22702,
+22717,
+22736,
+22750,
+22755,
+22780,
+22785,
+22822,
+22844,
+22859,
+22871,
+22909,
+22949,
+22962,
+22973,
+22979,
+22988,
+23007,
+23027,
+23035,
+23072,
+23082,
+23082,
+23109,
+23116,
+23130,
+23158,
+23166,
+23166,
+23172,
+23177,
+23189,
+23219,
+23219,
+23250,
+23273,
+23281,
+23281,
+23281,
+23281,
+23281,
+23287,
+23287,
+23299,
+23305,
+23315,
+23315,
+23321,
+23328,
+23334,
+23355,
+23355,
+23355,
+23355,
+23355,
+23366,
+23390,
+23396,
+23396,
+23396,
+23396,
+23402,
+23418,
+23424,
+23424,
+23438,
+23446,
+23446,
+23446,
+23500,
+23525,
+23569,
+23592,
+23592,
+23592,
+23605,
+23614,
+23614,
+23614,
+23627,
+23633,
+23657,
+23673,
+23673,
+23673,
+23689,
+23689,
+23701,
+23701,
+23701,
+23713,
+23713,
+23713,
+23738,
+23758,
+23775,
+23775,
+23794,
+23794,
+23803,
+23803,
+23803,
+23803,
+23813,
+23826,
+23845,
+23845,
+23845,
+23845,
+23872,
+23872,
+23872,
+23888,
+23888,
+23900,
+23900,
+23906,
+23906,
+23906,
+23906,
+23906,
+23924,
+23924,
+23924,
+23930,
+23930,
+23939,
+23949,
+23971,
+23971,
+23971,
+24000,
+24012,
+24042,
+24042,
+24042,
+24042,
+24042,
+24070,
+24076,
+24094,
+24094,
+24094,
+24123,
+24123,
+24123,
+24134,
+24150,
+24150,
+24150,
+24150,
+24150,
+24150,
+24155,
+24179,
+24179,
+24189,
+24189,
+24189,
+24198,
+24198,
+24218,
+24218,
+24218,
+24234,
+24251,
+24257,
+24276,
+24305,
+24321,
+24321,
+24321,
+24334,
+24334,
+24334,
+24349,
+24356,
+24361,
+24372,
+24372,
+24372,
+24388,
+24396,
+24396,
+24402,
+24410,
+24410,
+24428,
+24428,
+24450,
+24450,
+24467,
+24485,
+24495,
+24495,
+24495,
+24507,
+24507,
+24514,
+24531,
+24531,
+24531,
+24531,
+24531,
+24537,
+24537,
+24558,
+24572,
+24584,
+24584,
+24601,
+24601,
+24607,
+24615,
+24615,
+24632,
+24632,
+24632,
+24632,
+24661,
+24676,
+24676,
+24724,
+24751,
+24751,
+24774,
+24774,
+24783,
+24793,
+24793,
+24793,
+24813,
+24819,
+24819,
+24826,
+24826,
+24842,
+24858,
+24872,
+24872,
+24890,
+24890,
+24890,
+24890,
+24890,
+24890,
+24890,
+24890,
+24890,
+24906,
+24906,
+24917,
+24928,
+24947,
+24947,
+24947,
+24947,
+24947,
+24947,
+24947,
+24947,
+24947,
+24963,
+24983,
+24991,
+24991,
+24991,
+24991,
+24991,
+24991,
+24991,
+24991,
+25007,
+25007,
+25007,
+25007,
+25007,
+25007,
+25007,
+25030,
+25040,
+25040,
+25040,
+25040,
+25040,
+25059,
+25097,
+25132,
+25149,
+25159,
+25169,
+25169,
+25169,
+25192,
+25192,
+25192,
+25192,
+25205,
+25205,
+25216,
+25221,
+25221,
+25233,
+25233,
+25240,
+25250,
+25256,
+25273,
+25273,
+25303,
+25321,
+25321,
+25321,
+25333,
+25333,
+25333,
+25333,
+25370,
+25370,
+25402,
+25418,
+25418,
+25439,
+25439,
+25454,
+25454,
+25454,
+25463,
+25477,
+25526,
+25526,
+25526,
+25526,
+25545,
+25562,
+25572,
+25572,
+25582,
+25582,
+25582,
+25597,
+25610,
+25634,
+25641,
+25641,
+25641,
+25668,
+25668,
+25675,
+25707,
+25727,
+25727,
+25741,
+25756,
+25756,
+25779,
+25811,
+25811,
+25811,
+25817,
+25817,
+25817,
+25827,
+25827,
+25827,
+25827,
+25827,
+25836,
+25836,
+25836,
+25836,
+25850,
+25850,
+25860,
+25884,
+25901,
+25922,
+25936,
+25946,
+25969,
+25969,
+25969,
+25969,
+25975,
+25975,
+25987,
+25987,
+26065,
+26065,
+26065,
+26084,
+26084,
+26103,
+26128,
+26141,
+26151,
+26169,
+26169,
+26169,
+26180,
+26191,
+26191,
+26191,
+26197,
+26197,
+26205,
+26211,
+26235,
+26235,
+26235,
+26235,
+26235,
+26235,
+26245,
+26245,
+26259,
+26259,
+26259,
+26259,
+26284,
+26284,
+26325,
+26325,
+26355,
+26364,
+26364,
+26402,
+26418,
+26418,
+26425,
+26432,
+26432,
+26454,
+26504,
+26513,
+26525,
+26525,
+26525,
+26525,
+26525,
+26545,
+26545,
+26571,
+26590,
+26597,
+26597,
+26597,
+26597,
+26597,
+26639,
+26648,
+26659,
+26666,
+26672,
+26672,
+26672,
+26672,
+26672,
+26694,
+26701,
+26701,
+26701,
+26724,
+26724,
+26746,
+26753,
+26774,
+26774,
+26774,
+26774,
+26806,
+26824,
+26824,
+26830,
+26852,
+26882,
+26882,
+26889,
+26889,
+26889,
+26889,
+26889,
+26889,
+26903,
+26911,
+26918,
+26918,
+26928,
+26948,
+26948,
+26970,
+26970,
+26985,
+26996,
+27045,
+27045,
+27058,
+27058,
+27068,
+27068,
+27104,
+27155,
+27155,
+27155,
+27155,
+27155,
+27172,
+27172,
+27172,
+27172,
+27189,
+27195,
+27195,
+27223,
+27223,
+27223,
+27223,
+27244,
+27290,
+27322,
+27332,
+27364,
+27371,
+27371,
+27371,
+27401,
+27401,
+27417,
+27417,
+27431,
+27470,
+27470,
+27470,
+27470,
+27484,
+27484,
+27484,
+27484,
+27484,
+27496,
+27496,
+27515,
+27515,
+27543,
+27560,
+27576,
+27604,
+27622,
+27631,
+27631,
+27638,
+27649,
+27672,
+27682,
+27682,
+27682,
+27707,
+27717,
+27724,
+27732,
+27755,
+27755,
+27755,
+27768,
+27768,
+27783,
+27789,
+27799,
+27799,
+27799,
+27818,
+27826,
+27838,
+27848,
+27848,
+27848,
+27855,
+27862,
+27862,
+27896,
+27921,
+27921,
+27943,
+27954,
+27954,
+27976,
+27976,
+27976,
+27985,
+28002,
+28012,
+28019,
+28034,
+28034,
+28046,
+28068,
+28097,
+28122,
+28122,
+28131,
+28137,
+28151,
+28151,
+28172,
+28190,
+28196,
+28211,
+28211,
+28264,
+28273,
+28286,
+28324,
+28324,
+28324,
+28354,
+28354,
+28361,
+28397,
+28417,
+28417,
+28424,
+28435,
+28461,
+28461,
+28470,
+28483,
+28483,
+28483,
+28483,
+28483,
+28483,
+28483,
+28515,
+28531,
+28531,
+28549,
+28575,
+28575,
+28575,
+28582,
+28599,
+28615,
+28630,
+28630,
+28672,
+28711,
+28723,
+28723,
+28731,
+28752,
+28752,
+28752,
+28763,
+28763,
+28775,
+28775,
+28775,
+28775,
+28775,
+28775,
+28805,
+28814,
+28830,
+28861,
+28882,
+28882,
+28902,
+28918,
+28937,
+28952,
+28959,
+28998,
+29009,
+29009,
+29009,
+29009,
+29019,
+29019,
+29019,
+29019,
+29019,
+29057,
+29069,
+29076,
+29076,
+29076,
+29076,
+29076,
+29082,
+29082,
+29082,
+29117,
+29117,
+29117,
+29117,
+29134,
+29134,
+29159,
+29159,
+29185,
+29185,
+29196,
+29196,
+29242,
+29248,
+29256,
+29280,
+29301,
+29307,
+29307,
+29307,
+29314,
+29314,
+29338,
+29356,
+29367,
+29367,
+29381,
+29391,
+29399,
+29399,
+29414,
+29434,
+29434,
+29441,
+29473,
+29484,
+29503,
+29520,
+29520,
+29548,
+29565,
+29572,
+29572,
+29572,
+29572,
+29572,
+29597,
+29597,
+29620,
+29655,
+29660,
+29660,
+29660,
+29667,
+29674,
+29688,
+29698,
+29705,
+29728,
+29740,
+29740,
+29761,
+29761,
+29767,
+29780,
+29787,
+29794,
+29794,
+29807,
+29820,
+29820,
+29820,
+29820,
+29832,
+29844,
+29855,
+29855,
+29855,
+29867,
+29867,
+29867,
+29867,
+29881,
+29881,
+29905,
+29905,
+29905,
+29923,
+29923,
+29948,
+29948,
+29948,
+29976,
+29986,
+29996,
+29996,
+30030,
+30030,
+30054,
+30054,
+30070,
+30070,
+30070,
+30077,
+30077,
+30087,
+30107,
+30107,
+30115,
+30141,
+30178,
+30178,
+30201,
+30201,
+30201,
+30207,
+30229,
+30239,
+30254,
+30268,
+30277,
+30311,
+30323,
+30323,
+30331,
+30331,
+30331,
+30331,
+30331,
+30353,
+30365,
+30374,
+30374,
+30374,
+30380,
+30380,
+30380,
+30380,
+30410,
+30410,
+30410,
+30443,
+30443,
+30453,
+30462,
+30472,
+30472,
+30472,
+30472,
+30472,
+30472,
+30489,
+30500,
+30500,
+30500,
+30532,
+30532,
+30553,
+30577,
+30599,
+30599,
+30609,
+30640,
+30640,
+30640,
+30664,
+30676,
+30676,
+30676,
+30692,
+30719,
+30728,
+30728,
+30742,
+30742,
+30749,
+30749,
+30760,
+30770,
+30770,
+30783,
+30783,
+30783,
+30804,
+30847,
+30847,
+30847,
+30847,
+30857,
+30857,
+30857,
+30857,
+30875,
+30875,
+30895,
+30895,
+30921,
+30926,
+30926,
+30926,
+30945,
+30945,
+30945,
+30945,
+30959,
+30959,
+30959,
+30959,
+30972,
+30972,
+30984,
+31011,
+31011,
+31048,
+31056,
+31056,
+31070,
+31077,
+31077,
+31110,
+31110,
+31115,
+31122,
+31139,
+31159,
+31159,
+31165,
+31171,
+31171,
+31197,
+31204,
+31211,
+31211,
+31221,
+31228,
+31228,
+31245,
+31245,
+31245,
+31252,
+31265,
+31265,
+31265,
+31265,
+31294,
+31305,
+31320,
+31333,
+31333,
+31333,
+31343,
+31350,
+31357,
+31369,
+31369,
+31379,
+31385,
+31391,
+31407,
+31407,
+31407,
+31423,
+31423,
+31423,
+31434,
+31454,
+31470,
+31511,
+31521,
+31521,
+31521,
+31542,
+31582,
+31582,
+31597,
+31597,
+31597,
+31614,
+31623,
+31645,
+31645,
+31661,
+31661,
+31669,
+31669,
+31676,
+31676,
+31706,
+31720,
+31726,
+31743,
+31785,
+31804,
+31817,
+31817,
+31835,
+31846,
+31863,
+31885,
+31885,
+31896,
+31907,
+31907,
+31907,
+31922,
+31922,
+31929,
+31929,
+31929,
+31936,
+31943,
+31949,
+31949,
+31949,
+31959,
+32006,
+32024,
+32031,
+32031,
+32038,
+32063,
+32095,
+32095,
+32105,
+32105,
+32105,
+32105,
+32105,
+32125,
+32134,
+32140,
+32176,
+32185,
+32195,
+32195,
+32195,
+32195,
+32202,
+32202,
+32218,
+32236,
+32259,
+32294,
+32300,
+32305,
+32305,
+32305,
+32323,
+32337,
+32352,
+32359,
+32374,
+32381,
+32388,
+32388,
+32388,
+32402,
+32402,
+32402,
+32402,
+32418,
+32428,
+32428,
+32428,
+32450,
+32450,
+32450,
+32462,
+32467,
+32480,
+32480,
+32480,
+32487,
+32502,
+32509,
+32525,
+32560,
+32570,
+32583,
+32597,
+32623,
+32637,
+32644,
+32667,
+32707,
+32725,
+32725,
+32747,
+32747,
+32751,
+32758,
+32789,
+32807,
+32824,
+32824,
+32824,
+32824,
+32843,
+32843,
+32850,
+32876,
+32908,
+32915,
+32946,
+32965,
+32965,
+32982,
+33002,
+33009,
+33029,
+33064,
+33084,
+33098,
+33098,
+33098,
+33098,
+33110,
+33110,
+33151,
+33158,
+33180,
+33198,
+33205,
+33227,
+33227,
+33237,
+33237,
+33253,
+33258,
+33277,
+33292,
+33315,
+33315,
+33333,
+33348,
+33348,
+33348,
+33348,
+33348,
+33348,
+33348,
+33355,
+33355,
+33355,
+33390,
+33408,
+33423,
+33437,
+33452,
+33458,
+33465,
+33480,
+33480,
+33487,
+33494,
+33504,
+33511,
+33551,
+33551,
+33558,
+33589,
+33595,
+33595,
+33602,
+33627,
+33644,
+33668,
+33668,
+33668,
+33676,
+33676,
+33716,
+33728,
+33747,
+33747,
+33769,
+33775,
+33789,
+33803,
+33803,
+33810,
+33810,
+33810,
+33820,
+33820,
+33820,
+33820,
+33843,
+33843,
+33843,
+33879,
+33889,
+33889,
+33889,
+33903,
+33917,
+33931,
+33959,
+33993,
+34000,
+34014,
+34037,
+34043,
+34055,
+34055,
+34077,
+34083,
+34090,
+34099,
+34099,
+34115,
+34115,
+34133,
+34140,
+34167,
+34172,
+34184,
+34221,
+34245,
+34252,
+34252,
+34259,
+34318,
+34318,
+34325,
+34325,
+34352,
+34400,
+34415,
+34422,
+34422,
+34431,
+34438,
+34445,
+34445,
+34473,
+34473,
+34489,
+34489,
+34489,
+34489,
+34499,
+34499,
+34499,
+34516,
+34536,
+34551,
+34564,
+34580,
+34580,
+34580,
+34589,
+34589,
+34589,
+34613,
+34648,
+34648,
+34648,
+34655,
+34664,
+34681,
+34681,
+34698,
+34698,
+34720,
+34736,
+34749,
+34749,
+34765,
+34778,
+34785,
+34795,
+34819,
+34819,
+34829,
+34841,
+34848,
+34854,
+34854,
+34854,
+34878,
+34894,
+34894,
+34900,
+34917,
+34934,
+34940,
+34970,
+34998,
+34998,
+35004,
+35004,
+35012,
+35012,
+35012,
+35020,
+35020,
+35032,
+35038,
+35062,
+35062,
+35062,
+35068,
+35068,
+35082,
+35092,
+35096,
+35107,
+35118,
+35134,
+35155,
+35155,
+35166,
+35178,
+35178,
+35195,
+35201,
+35201,
+35201,
+35226,
+35226,
+35226,
+35226,
+35256,
+35262,
+35272,
+35280,
+35299,
+35332,
+35354,
+35354,
+35354,
+35370,
+35386,
+35417,
+35417,
+35460,
+35473,
+35478,
+35495,
+35504,
+35504,
+35518,
+35552,
+35589,
+35624,
+35624,
+35637,
+35637,
+35643,
+35643,
+35669,
+35682,
+35695,
+35702,
+35709,
+35709,
+35726,
+35739,
+35749,
+35756,
+35756,
+35778,
+35803,
+35810,
+35829,
+35883,
+35899,
+35905,
+35911,
+35911,
+35923,
+35947,
+35954,
+35980,
+35987,
+36034,
+36052,
+36063,
+36095,
+36106,
+36106,
+36113,
+36120,
+36140,
+36140,
+36153,
+36160,
+36160,
+36167,
+36203,
+36203,
+36218,
+36218,
+36225,
+36253,
+36259,
+36284,
+36296,
+36310,
+36324,
+36331,
+36344,
+36367,
+36367,
+36367,
+36412,
+36412,
+36422,
+36463,
+36463,
+36463,
+36479,
+36490,
+36513,
+36520,
+36520,
+36527,
+36527,
+36527,
+36540,
+36574,
+36594,
+36594,
+36605,
+36621,
+36621,
+36641,
+36641,
+36641,
+36659,
+36682,
+36682,
+36682,
+36682,
+36705,
+36705,
+36705,
+36720,
+36720,
+36755,
+36755,
+36771,
+36771,
+36771,
+36788,
+36806,
+36835,
+36845,
+36875,
+36875,
+36903,
+36921,
+36928,
+36928,
+36940,
+36940,
+36940,
+36966,
+36966,
+36973,
+36983,
+36998,
+37004,
+37014,
+37024,
+37024,
+37032,
+37038,
+37038,
+37061,
+37074,
+37074,
+37091,
+37098,
+37105,
+37105,
+37133,
+37141,
+37141,
+37148,
+37191,
+37191,
+37197,
+37197,
+37210,
+37224,
+37224,
+37231,
+37250,
+37257,
+37273,
+37273,
+37280,
+37287,
+37294,
+37300,
+37307,
+37330,
+37348,
+37348,
+37359,
+37359,
+37359,
+37377,
+37392,
+37398,
+37412,
+37431,
+37469,
+37486,
+37508,
+37517,
+37535,
+37535,
+37542,
+37542,
+37549,
+37549,
+37549,
+37549,
+37556,
+37576,
+37576,
+37583,
+37590,
+37597,
+37604,
+37604,
+37621,
+37635,
+37676,
+37676,
+37704,
+37711,
+37728,
+37728,
+37737,
+37737,
+37737,
+37750,
+37757,
+37778,
+37785,
+37785,
+37819,
+37826,
+37833,
+37843,
+37850,
+37869,
+37914,
+37921,
+37935,
+37942,
+37949,
+37982,
+38013,
+38013,
+38013,
+38023,
+38057,
+38077,
+38097,
+38110,
+38117,
+38123,
+38133,
+38133,
+38133,
+38140,
+38140,
+38148,
+38159,
+38179,
+38192,
+38205,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38218,
+38225,
+38225,
+38230,
+38246,
+38258,
+38280,
+38287,
+38294,
+38294,
+38294,
+38301,
+38318,
+38318,
+38340,
+38371,
+38371,
+38384,
+38420,
+38440,
+38453,
+38481,
+38506,
+38522,
+38534,
+38559,
+38559,
+38559,
+38564,
+38564,
+38581,
+38604,
+38604,
+38611,
+38620,
+38626,
+38635,
+38635,
+38635,
+38666,
+38674,
+38688,
+38693,
+38710,
+38722,
+38722,
+38722,
+38729,
+38734,
+38752,
+38792,
+38818,
+38825,
+38861,
+38902,
+38934,
+38949,
+38949,
+38960,
+38969,
+38985,
+38985,
+38996,
+39013,
+39024,
+39024,
+39032,
+39061,
+39074,
+39089,
+39123,
+39123,
+39123,
+39140,
+39161,
+39180,
+39206,
+39215,
+39254,
+39261,
+39277,
+39284,
+39314,
+39314,
+39330,
+39340,
+39340,
+39371,
+39371,
+39392,
+39430,
+39430,
+39437,
+39444,
+39461,
+39468,
+39468,
+39485,
+39517,
+39524,
+39538,
+39543,
+39548,
+39555,
+39581,
+39588,
+39588,
+39609,
+39609,
+39616,
+39652,
+39670,
+39677,
+39677,
+39684,
+39691,
+39702,
+39717,
+39717,
+39717,
+39724,
+39749,
+39760,
+39766,
+39775,
+39791,
+39791,
+39814,
+39827,
+39827,
+39837,
+39859};
+
+static const char tldData[] = {
+"com.cn\0"
+"com.co\0"
+"hb.cn\0"
+"med.br\0conf.lv\0wallonie.museum\0"
+"namsos.no\0"
+"\xe7\xb6\xb2\xe7\xbb\x9c.hk\0farmers.museum\0rel.pl\0"
+"com.cu\0"
+"military.museum\0"
+"*.jm\0convent.museum\0cymru.museum\0malvik.no\0"
+"univ.sn\0"
+"gliding.aero\0"
+"wodzislaw.pl\0"
+"com.dm\0!pref.iwate.jp\0tran\xc3\xb8y.no\0pila.pl\0"
+"mb.it\0*.ke\0lib.ri.us\0"
+"com.ec\0*.kh\0tr\xc3\xb8gstad.no\0"
+"com.ee\0"
+"mobi.gp\0"
+"gran.no\0"
+"wa.gov.au\0"
+"com.dz\0kg.kr\0"
+"zoological.museum\0gjerstad.no\0haugesund.no\0kharkov.ua\0"
+"walbrzych.pl\0"
+"civilization.museum\0"
+"ha.no\0"
+"*.kw\0"
+"med.ec\0com.es\0"
+"med.ee\0otago.museum\0svelvik.no\0"
+"art.ht\0amber.museum\0elvendrell.museum\0rost.no\0"
+"jx.cn\0gratangen.no\0"
+"association.aero\0ca.it\0"
+"zaporizhzhe.ua\0"
+"com.fr\0"
+"szex.hu\0"
+"e-burg.ru\0"
+"com.ge\0bokn.no\0"
+"mordovia.ru\0"
+"com.gh\0*.mm\0"
+"com.gi\0z.se\0"
+"cahcesuolo.no\0"
+"hurdal.no\0joshkar-ola.ru\0"
+"cadaques.museum\0ma.us\0"
+"a.bg\0"
+"com.gn\0bozen.it\0tambov.ru\0"
+"*.gifu.jp\0*.tokyo.jp\0*.mt\0"
+"com.gp\0travel\0cc.tx.us\0"
+"com.gr\0hemne.no\0"
+"*.ni\0"
+"*.mz\0"
+"cc.il.us\0"
+"com.gy\0"
+"zj.cn\0oksnes.no\0museum.tt\0"
+"com.hk\0*.np\0"
+"rc.it\0baseball.museum\0"
+"com.hn\0exhibition.museum\0"
+"h\xc3\xa1""bmer.no\0"
+"com.hr\0"
+"fg.it\0stathelle.no\0defense.tn\0"
+"com.ht\0"
+"qld.gov.au\0*.nz\0"
+"davvenj\xc3\xa1rga.no\0*.om\0"
+"vang.no\0"
+"*.kumamoto.jp\0"
+"vercelli.it\0usenet.pl\0"
+"com.io\0stalbans.museum\0"
+"com.iq\0"
+"*.pg\0"
+"com.is\0klabu.no\0skiptvet.no\0"
+"med.ht\0field.museum\0"
+"gr.it\0gj\xc3\xb8vik.no\0tromsa.no\0lib.mi.us\0"
+"ca.na\0"
+"hagebostad.no\0k12.ma.us\0"
+"*.qa\0"
+"*.niigata.jp\0"
+"monzaebrianza.it\0com.jo\0comunica\xc3\xa7\xc3\xb5""es.museum\0"
+"gr.jp\0"
+"ballangen.no\0*.py\0"
+"scienceandindustry.museum\0"
+"nuoro.it\0com.kg\0"
+"com.ki\0"
+"im.it\0idv.tw\0"
+"*.akita.jp\0com.km\0r\xc3\xb8ros.no\0sopot.pl\0"
+"!pref.yamanashi.jp\0"
+"com.kp\0"
+"!pref.kochi.jp\0com.la\0"
+"com.lb\0"
+"com.lc\0stjordalshalsen.no\0sigdal.no\0cc.nm.us\0"
+"samnanger.no\0"
+"drobak.no\0"
+"vt.it\0"
+"catering.aero\0com.ky\0"
+"com.kz\0cc.ca.us\0"
+"com.lk\0"
+"grosseto.it\0mosvik.no\0"
+"namsskogan.no\0"
+"loten.no\0"
+"chirurgiens-dentistes.fr\0"
+"com.lr\0bremanger.no\0"
+"gs.cn\0"
+"com.lv\0lib.co.us\0"
+"com.mg\0"
+"passenger-association.aero\0"
+"com.ly\0yekaterinburg.ru\0"
+"vladivostok.ru\0"
+"com.mk\0beeldengeluid.museum\0"
+"com.ml\0"
+"art.pl\0"
+"com.mo\0"
+"britishcolumbia.museum\0tx.us\0"
+"com.na\0sakhalin.ru\0*.sv\0"
+"mc.it\0"
+"amsterdam.museum\0udm.ru\0"
+"com.mu\0"
+"com.mv\0com.nf\0"
+"com.mw\0com.ng\0il.us\0"
+"geometre-expert.fr\0com.mx\0"
+"med.ly\0com.my\0"
+"ag.it\0"
+"*.tr\0"
+"!pref.oita.jp\0"
+"hoyanger.no\0skedsmo.no\0"
+"com.nr\0turystyka.pl\0"
+"koebenhavn.museum\0"
+"quebec.museum\0"
+"stord.no\0*.uk\0"
+"act.au\0"
+"br.it\0cb.it\0gyeonggi.kr\0jobs.tt\0lib.hi.us\0"
+"*.ve\0"
+"*.saga.jp\0wildlife.museum\0com.pa\0"
+"monzabrianza.it\0sciencehistory.museum\0stange.no\0oskol.ru\0principe.st\0*.uy\0"
+"plaza.museum\0com.pe\0"
+"com.pf\0"
+"eigersund.no\0"
+"com.ph\0"
+"manx.museum\0marylhurst.museum\0"
+"md.ci\0pi.it\0schweiz.museum\0com.pk\0"
+"grp.lk\0fr\xc3\xb8ya.no\0com.pl\0press.se\0"
+"us.com\0"
+"b.bg\0cremona.it\0communication.museum\0art.sn\0"
+"med.pa\0"
+"com.pr\0"
+"com.ps\0"
+"com.pt\0"
+"k12.in.us\0"
+"ah.cn\0bahcavuotna.no\0"
+"sondrio.it\0arkhangelsk.ru\0"
+"cargo.aero\0"
+"council.aero\0"
+"museum.mv\0hattfjelldal.no\0spydeberg.no\0med.pl\0"
+"niepce.museum\0museum.mw\0"
+"anthropology.museum\0"
+"pharmacien.fr\0smola.no\0"
+"fin.ec\0"
+"selbu.no\0"
+"workinggroup.aero\0nm.us\0"
+"museum.no\0com.re\0"
+"cc.vt.us\0"
+"village.museum\0"
+"ca.us\0"
+"*.sapporo.jp\0"
+"teramo.it\0"
+"com.ro\0"
+"*.ye\0"
+"com.sa\0"
+"com.sb\0"
+"so.it\0com.sc\0"
+"jolster.no\0com.sd\0"
+"com.ru\0"
+"com.rw\0com.sg\0"
+"sydney.museum\0"
+"sa.edu.au\0"
+"tom.ru\0"
+"com.sl\0*.za\0"
+"\xe7\xbd\x91\xe7\xb5\xa1.hk\0naturbruksgymn.se\0com.sn\0"
+"assedic.fr\0com.so\0"
+"!pref.mie.jp\0*.yu\0"
+"med.sa\0"
+"newspaper.museum\0holmestrand.no\0dnepropetrovsk.ua\0"
+"christiansburg.museum\0roan.no\0"
+"pesaro-urbino.it\0med.sd\0com.st\0"
+"s\xc3\xb8gne.no\0"
+"nuernberg.museum\0"
+"*.zm\0"
+"com.sy\0"
+"*.nagano.jp\0com.tj\0"
+"nt.gov.au\0news.hu\0paderborn.museum\0"
+"boston.museum\0"
+"com.tn\0"
+"com.to\0"
+"broadcast.museum\0"
+"com.ua\0"
+"*.zw\0"
+"baikal.ru\0"
+"bykle.no\0com.tt\0"
+"verdal.no\0"
+"roros.no\0"
+"fi.cr\0carboniaiglesias.it\0chuvashia.ru\0com.tw\0"
+"k12.ca.us\0"
+"eidsvoll.no\0"
+"*.ishikawa.jp\0"
+"dolls.museum\0"
+"naval.museum\0"
+"karasjok.no\0tysvar.no\0"
+"bielawa.pl\0com.vc\0"
+"svalbard.no\0deatnu.no\0rnd.ru\0"
+"grandrapids.museum\0"
+"bauern.museum\0k12.pr.us\0"
+"press.ma\0"
+"*.kagawa.jp\0fribourg.museum\0przeworsk.pl\0com.vi\0"
+"com.uz\0"
+"babia-gora.pl\0"
+"com.vn\0"
+"med.pro\0"
+"suedtirol.it\0kursk.ru\0"
+"bonn.museum\0"
+"lt.it\0"
+"design.aero\0microlight.aero\0americanantiques.museum\0meland.no\0"
+"insurance.aero\0aarborte.no\0"
+"kh.ua\0"
+"macerata.it\0architecture.museum\0"
+"rovigo.it\0rawa-maz.pl\0"
+"store.nf\0levanger.no\0"
+"b\xc3\xa1jddar.no\0"
+"not.br\0"
+"com.ws\0"
+"!pref.kagawa.jp\0"
+"!omanpost.om\0"
+"vt.us\0"
+"gs.ah.no\0vladikavkaz.ru\0"
+"no.it\0"
+"in.na\0szkola.pl\0a.se\0"
+"aid.pl\0"
+"workshop.museum\0vegarshei.no\0"
+"sund.no\0"
+"bs.it\0flora.no\0"
+"agriculture.museum\0"
+"koeln.museum\0"
+"minnesota.museum\0k12.il.us\0"
+"froya.no\0"
+"aeroport.fr\0"
+"davvenjarga.no\0zgora.pl\0ivano-frankivsk.ua\0"
+"*.gunma.jp\0"
+"amot.no\0"
+"mus.br\0chungbuk.kr\0"
+"ggf.br\0lorenskog.no\0"
+"jeonbuk.kr\0"
+"k12.vi.us\0"
+"c.bg\0sande.more-og-romsdal.no\0"
+"perugia.it\0"
+"massa-carrara.it\0"
+"michigan.museum\0"
+"archaeology.museum\0mosj\xc3\xb8""en.no\0czest.pl\0koenig.ru\0\xe0\xb6\xbd\xe0\xb6\x82\xe0\xb6\x9a\xe0\xb7\x8f\0"
+"mobi.tt\0"
+"kraanghke.no\0"
+"cc.in.us\0"
+"re.it\0lib.vt.us\0"
+"dell-ogliastra.it\0"
+"s\xc3\xb8mna.no\0"
+"k12.wv.us\0"
+"gok.pk\0fh.se\0"
+"luzern.museum\0"
+"fi.it\0swidnica.pl\0"
+"cbg.ru\0"
+"latina.it\0"
+"vibovalentia.it\0"
+"modum.no\0"
+"safety.aero\0"
+"sp.it\0"
+"science.museum\0ah.no\0"
+"norddal.no\0"
+"cc.na\0"
+"re.kr\0"
+"dielddanuorri.no\0"
+"force.museum\0"
+"torino.it\0cc.md.us\0"
+"artanddesign.museum\0pisz.pl\0"
+"olsztyn.pl\0"
+"unsa.ba\0rade.no\0vinnica.ua\0"
+"in.rs\0astrakhan.ru\0"
+"sogne.no\0"
+"homebuilt.aero\0"
+"polkowice.pl\0"
+"hole.no\0health.vn\0"
+"fj.cn\0"
+"davvesiida.no\0"
+"vic.au\0"
+"kongsberg.no\0"
+"pub.sa\0"
+"vv.it\0"
+"!pref.tottori.jp\0"
+"*.sendai.jp\0in.th\0"
+"lib.pa.us\0"
+"chiropractic.museum\0"
+"mobi.na\0aca.pro\0"
+"konyvelo.hu\0sciencecenters.museum\0"
+"he.cn\0"
+"in.ua\0"
+"!city.nagoya.jp\0"
+"muenchen.museum\0"
+"psi.br\0"
+"maryland.museum\0"
+"!statecouncil.om\0"
+"tr\xc3\xa6na.no\0"
+"!pref.yamagata.jp\0jewishart.museum\0"
+"lu.it\0me.it\0"
+"chel.ru\0"
+"tatarstan.ru\0"
+"adult.ht\0in.us\0"
+"kafjord.no\0"
+"\xd7\x99\xd7\xa8\xd7\x95\xd7\xa9\xd7\x9c\xd7\x99\xd7\x9d.museum\0"
+"net.ac\0k12.ec\0"
+"net.ae\0bashkiria.ru\0"
+"net.af\0!omantel.om\0"
+"net.ag\0"
+"net.ai\0"
+"!pref.toyama.jp\0"
+"net.al\0timekeeping.museum\0"
+"net.an\0design.museum\0fin.tn\0"
+"ethnology.museum\0"
+"perso.ht\0asker.no\0b.se\0"
+"net.ba\0"
+"net.bb\0flanders.museum\0"
+"mincom.tn\0"
+"frana.no\0"
+"bt.it\0"
+"net.bh\0"
+"auto.pl\0"
+"net.az\0"
+"treviso.it\0"
+"war.museum\0"
+"net.bm\0"
+"langevag.no\0m\xc3\xa5lselv.no\0"
+"net.bo\0"
+"gol.no\0"
+"folkebibl.no\0"
+"net.br\0"
+"net.bs\0troandin.no\0saotome.st\0lib.tn.us\0"
+"md.us\0k12.ut.us\0"
+"d.bg\0cambridge.museum\0\xc3\xa5s.no\0"
+"net.ci\0"
+"net.bz\0"
+"sch.ae\0undersea.museum\0odda.no\0"
+"net.cn\0"
+"net.co\0c.la\0"
+"gliwice.pl\0"
+"aurskog-h\xc3\xb8land.no\0"
+"andria-trani-barletta.it\0"
+"net.cu\0loab\xc3\xa1t.no\0"
+"rep.kp\0"
+"\xe7\xbb\x84\xe7\xb9\x94.hk\0"
+"gallery.museum\0"
+"\xc3\xb8rland.no\0"
+"store.ro\0"
+"net.dm\0"
+"somna.no\0"
+"hemnes.no\0"
+"ringebu.no\0k12.ky.us\0"
+"net.ec\0"
+"dn.ua\0"
+"tarnobrzeg.pl\0"
+"soc.lk\0"
+"romsa.no\0"
+"bamble.no\0"
+"net.dz\0lutsk.ua\0"
+"barlettatraniandria.it\0ta.it\0countryestate.museum\0"
+"kaszuby.pl\0"
+"*.yamaguchi.jp\0cranbrook.museum\0store.st\0"
+"southcarolina.museum\0lib.md.us\0"
+"textile.museum\0"
+"cheltenham.museum\0hurum.no\0"
+"*.oita.jp\0"
+"shop.ht\0cc.me.us\0"
+"shop.hu\0turin.it\0"
+"louvre.museum\0"
+"k12.ar.us\0"
+"consulting.aero\0"
+"gv.ao\0"
+"sauherad.no\0"
+"gv.at\0net.ge\0"
+"ostre-toten.no\0lib.ok.us\0"
+"net.gg\0pilots.museum\0"
+"2000.hu\0geology.museum\0"
+"net.gn\0"
+"mazowsze.pl\0bir.ru\0"
+"net.gp\0"
+"net.gr\0"
+"oxford.museum\0"
+"per.la\0"
+"eastafrica.museum\0"
+"meeres.museum\0"
+"net.gy\0*.shizuoka.jp\0"
+"\xe5\x95\x86\xe6\xa5\xad.tw\0"
+"net.hk\0"
+"net.hn\0"
+"philadelphiaarea.museum\0"
+"osen.no\0"
+"net.ht\0net.id\0"
+"fundacio.museum\0"
+"j\xc3\xb8rpeland.no\0"
+"\xe6\x95\x99\xe8\x82\xb2.hk\0"
+"divtasvuodna.no\0"
+"student.aero\0sch.gg\0net.im\0"
+"\xe7\xbd\x91\xe7\xbb\x9c.cn\0net.in\0"
+"net.iq\0"
+"net.ir\0"
+"net.is\0"
+"net.je\0"
+"kepno.pl\0lapy.pl\0"
+"per.nf\0"
+"gov\0*.shimane.jp\0"
+"artcenter.museum\0"
+"k\xc3\xa5""fjord.no\0"
+"net.jo\0"
+"eu.int\0"
+"c.se\0"
+"net.kg\0"
+"ce.it\0net.ki\0"
+"sch.id\0os.hedmark.no\0"
+"columbus.museum\0"
+"arteducation.museum\0"
+"net.kn\0"
+"kr.com\0"
+"net.la\0bushey.museum\0cc.gu.us\0"
+"net.lb\0"
+"net.lc\0"
+"gs.bu.no\0"
+"e164.arpa\0"
+"chieti.it\0labour.museum\0"
+"sch.ir\0creation.museum\0krodsherad.no\0"
+"net.ky\0"
+"net.kz\0me.us\0"
+"e.bg\0sch.je\0net.lk\0"
+"zlg.br\0suwalki.pl\0"
+"\xe5\x80\x8b\xe4\xba\xba.hk\0net.ma\0"
+"net.lr\0"
+"sch.jo\0notaires.km\0net.me\0"
+"net.lv\0karate.museum\0"
+"net.ly\0karm\xc3\xb8y.no\0"
+"rg.it\0"
+"net.mk\0"
+"net.ml\0evenes.no\0"
+"ngo.lk\0net.mo\0egyptian.museum\0"
+"marine.ru\0"
+"realestate.pl\0"
+"net.mu\0"
+"net.mv\0net.nf\0"
+"net.mw\0net.ng\0gda.pl\0"
+"net.mx\0"
+"freemasonry.museum\0net.my\0enebakk.no\0"
+"karlsoy.no\0"
+"\xe7\xbd\x91\xe7\xbb\x9c.hk\0\xc3\xb8rskog.no\0"
+"randaberg.no\0"
+"club.aero\0"
+"certification.aero\0sr.it\0"
+"center.museum\0so.gov.pl\0"
+"caa.aero\0"
+"sch.lk\0tvedestrand.no\0"
+"net.nr\0"
+"luroy.no\0"
+"aukra.no\0s\xc3\xa1lat.no\0lib.me.us\0"
+"ddr.museum\0"
+"york.museum\0stryn.no\0k12.nm.us\0"
+"per.sg\0"
+"judaica.museum\0"
+"verona.it\0"
+"agdenes.no\0"
+"cng.br\0sch.ly\0"
+"net.pa\0"
+"author.aero\0"
+"naturalhistory.museum\0steiermark.museum\0bu.no\0"
+"sn\xc3\xa5sa.no\0net.pe\0"
+"net.ph\0"
+"savannahga.museum\0batsfjord.no\0lib.oh.us\0"
+"net.pk\0"
+"net.pl\0"
+"net.pn\0"
+"washingtondc.museum\0"
+"net.pr\0"
+"net.ps\0"
+"net.pt\0"
+"nordkapp.no\0"
+"emergency.aero\0krokstadelva.no\0"
+"satx.museum\0ngo.ph\0omsk.ru\0"
+"texas.museum\0"
+"ngo.pl\0"
+"mantova.it\0gu.us\0"
+"!pref.shiga.jp\0isa.us\0"
+"usa.museum\0"
+"gb.net\0k12.vi\0"
+"iveland.no\0"
+"tempio-olbia.it\0"
+"net.sa\0"
+"net.sb\0"
+"works.aero\0net.sc\0komvux.se\0"
+"net.sd\0"
+"net.ru\0"
+"0.bg\0"
+"forlicesena.it\0net.rw\0net.sg\0"
+"klodzko.pl\0"
+"detroit.museum\0wegrow.pl\0"
+"net.sl\0"
+"glogow.pl\0"
+"store.bb\0air.museum\0"
+"net.so\0"
+"katowice.pl\0"
+"nsk.ru\0"
+"pisa.it\0eid.no\0"
+"net.st\0"
+"film.hu\0"
+"tuva.ru\0d.se\0"
+"net.th\0"
+"net.sy\0"
+"viterbo.it\0tsaritsyn.ru\0perso.sn\0net.tj\0"
+"lib.gu.us\0"
+"plc.co.im\0sec.ps\0"
+"r\xc3\xa1hkker\xc3\xa1vju.no\0kazimierz-dolny.pl\0net.tn\0"
+"net.to\0"
+"veterinaire.km\0"
+"net.ua\0"
+"info.ht\0net.tt\0"
+"info.hu\0"
+"exchange.aero\0"
+"sch.sa\0net.tw\0"
+"andriatranibarletta.it\0perso.tn\0"
+"f.bg\0malselv.no\0"
+"net.vc\0"
+"trana.no\0"
+"ns.ca\0"
+"net.vi\0"
+"lucca.it\0oristano.it\0"
+"usarts.museum\0net.vn\0"
+"gon.pk\0"
+"pl.ua\0"
+"eastcoast.museum\0"
+"novara.it\0"
+"k12.ks.us\0"
+"dp.ua\0"
+"nesseby.no\0"
+"!pref.wakayama.jp\0"
+"repbody.aero\0"
+"jamison.museum\0lugansk.ua\0"
+"ss.it\0"
+"alessandria.it\0"
+"hadsel.no\0net.ws\0"
+"\xe0\xae\x9a\xe0\xae\xbf\xe0\xae\x99\xe0\xaf\x8d\xe0\xae\x95\xe0\xae\xaa\xe0\xaf\x8d\xe0\xae\xaa\xe0\xaf\x82\xe0\xae\xb0\xe0\xaf\x8d\0"
+"veterinaire.fr\0leirfjord.no\0"
+"massacarrara.it\0north.museum\0"
+"project.museum\0"
+"other.nf\0"
+"k12.nh.us\0"
+"mat.br\0artgallery.museum\0"
+"sr.gov.pl\0"
+"gamvik.no\0"
+"info.ec\0lancashire.museum\0"
+"fm.br\0ltd.co.im\0"
+"americana.museum\0southwest.museum\0cc.ak.us\0"
+"enna.it\0lunner.no\0"
+"v\xc3\xa5gan.no\0"
+"mari.ru\0"
+"accident-investigation.aero\0"
+"sor-aurdal.no\0lib.ny.us\0"
+"novosibirsk.ru\0"
+"bjugn.no\0"
+"n\xc3\xa6r\xc3\xb8y.no\0ostrowwlkp.pl\0"
+"info.bb\0foundation.museum\0"
+"brand.se\0"
+"info.at\0!pref.akita.jp\0l\xc3\xb8ten.no\0"
+"coal.museum\0miners.museum\0"
+"glass.museum\0"
+"info.az\0"
+"frog.museum\0szczytno.pl\0nov.ru\0"
+"sunndal.no\0"
+"gen.in\0"
+"gx.cn\0"
+"web.co\0*.mie.jp\0hobol.no\0\xe5\x8f\xb0\xe6\xb9\xbe\0"
+"logistics.aero\0plo.ps\0"
+"erotika.hu\0"
+"torsken.no\0"
+"exeter.museum\0"
+"info.co\0"
+"selje.no\0"
+"storfjord.no\0"
+"barum.no\0lind\xc3\xa5s.no\0"
+"leasing.aero\0"
+"championship.aero\0fst.br\0"
+"lierne.no\0"
+"!gobiernoelectronico.ar\0""1.bg\0"
+"corporation.museum\0"
+"al.it\0*.miyagi.jp\0"
+"*.aomori.jp\0"
+"\xd8\xa7\xd9\x84\xd8\xa7\xd8\xb1\xd8\xaf\xd9\x86\0"
+"amursk.ru\0"
+"vestvagoy.no\0"
+"\xd8\xa7\xdb\x8c\xd8\xb1\xd8\xa7\xd9\x86.ir\0cc.fl.us\0"
+"os.hordaland.no\0"
+"pistoia.it\0"
+"tver.ru\0e.se\0"
+"res.in\0*.yamagata.jp\0syzran.ru\0"
+"capebreton.museum\0sandnessj\xc3\xb8""en.no\0"
+"ternopil.ua\0"
+"shop.pl\0"
+"tank.museum\0"
+"m\xc3\xa5s\xc3\xb8y.no\0"
+"potenza.it\0time.museum\0"
+"mjondalen.no\0"
+"eng.br\0nedre-eiker.no\0"
+"air-surveillance.aero\0"
+"nt.au\0am.br\0pn.it\0"
+"oystre-slidre.no\0ug.gov.pl\0"
+"g.bg\0nesodden.no\0vologda.ru\0"
+"parma.it\0tula.ru\0"
+"*.nara.jp\0ak.us\0"
+"nt.ca\0konin.pl\0"
+"kiev.ua\0"
+"skierv\xc3\xa1.no\0vestre-toten.no\0"
+"ri.it\0botanical.museum\0farsund.no\0veg\xc3\xa5rshei.no\0dagestan.ru\0"
+"ind.br\0k-uralsk.ru\0"
+"rahkkeravju.no\0cmw.ru\0"
+"canada.museum\0"
+"fm.it\0"
+"cc.wi.us\0"
+"web.id\0aver\xc3\xb8y.no\0"
+"dudinka.ru\0"
+"baghdad.museum\0fitjar.no\0grane.no\0"
+"gs.fm.no\0"
+"sumy.ua\0"
+"al.no\0"
+"westfalen.museum\0"
+"oregon.museum\0"
+"bruxelles.museum\0elk.pl\0"
+"planetarium.museum\0sn\xc3\xa5""ase.no\0"
+"s\xc3\xb8rreisa.no\0"
+"gs.st.no\0skien.no\0"
+"bible.museum\0ivanovo.ru\0"
+"avellino.it\0"
+"tgory.pl\0"
+"family.museum\0"
+"ppg.br\0k12.as.us\0"
+"trader.aero\0gorlice.pl\0"
+"cc.al.us\0"
+"ogliastra.it\0"
+"is.it\0lib.nv.us\0"
+"dr.na\0"
+"media.hu\0nesna.no\0fl.us\0"
+"uri.arpa\0"
+"bjerkreim.no\0"
+"charter.aero\0"
+"genova.it\0"
+"it.ao\0botany.museum\0hapmir.no\0"
+"educational.museum\0"
+"helsinki.museum\0"
+"memorial.museum\0"
+"web.lk\0pharmacy.museum\0"
+"aircraft.aero\0appspot.com\0"
+"ferrara.it\0beskidy.pl\0"
+"hi.cn\0"
+"taxi.aero\0flekkefjord.no\0"
+"varoy.no\0"
+"ragusa.it\0ambulance.museum\0"
+"can.museum\0"
+"*.osaka.jp\0isleofman.museum\0fm.no\0warmia.pl\0"
+"educator.aero\0asmatart.museum\0"
+"mi.it\0"
+"kutno.pl\0"
+"skedsmokorset.no\0"
+"2.bg\0"
+"*.kagoshima.jp\0km.ua\0"
+"!city.sendai.jp\0"
+"web.nf\0st.no\0cc.ri.us\0"
+"reggiocalabria.it\0"
+"wi.us\0"
+"ancona.it\0newjersey.museum\0nnov.ru\0"
+"f.se\0"
+"ind.in\0"
+"info.vn\0"
+"andoy.no\0"
+"ch.it\0fredrikstad.no\0guovdageaidnu.no\0"
+"fjaler.no\0"
+"sa.com\0"
+"gs.nt.no\0"
+"masfjorden.no\0"
+"pordenone.it\0"
+"po.it\0basel.museum\0"
+"chambagri.fr\0"
+"h.bg\0web.pk\0"
+"london.museum\0"
+"sciencecenter.museum\0\xe0\xb9\x84\xe0\xb8\x97\xe0\xb8\xa2\0"
+"unbi.ba\0augustow.pl\0"
+"wolomin.pl\0"
+"notaires.fr\0tcm.museum\0al.us\0"
+"nu.ca\0!pref.nagano.jp\0"
+"info.tn\0"
+"lib.wa.us\0"
+"ed.ao\0info.tt\0"
+"barreau.bj\0"
+"k12.wy.us\0"
+"pp.az\0gop.pk\0"
+"int\0"
+"l\xc3\xb8renskog.no\0podhale.pl\0"
+"voagat.no\0"
+"telekommunikation.museum\0"
+"qld.au\0"
+"te.it\0freiburg.museum\0snasa.no\0"
+"gjemnes.no\0"
+"sejny.pl\0"
+"media.pl\0"
+"skjak.no\0"
+"watchandclock.museum\0"
+"ed.ci\0pacific.museum\0"
+"theater.museum\0info.ro\0"
+"uk.com\0"
+"campobasso.it\0aquarium.museum\0tysv\xc3\xa6r.no\0"
+"kragero.no\0"
+"windmill.museum\0info.sd\0"
+"sologne.museum\0sande.m\xc3\xb8re-og-romsdal.no\0"
+"nt.no\0cc.mi.us\0"
+"ed.cr\0"
+"academy.museum\0zachpomor.pl\0"
+"tananger.no\0v\xc3\xa1rgg\xc3\xa1t.no\0ri.us\0"
+"federation.aero\0"
+"web.tj\0"
+"matta-varjjat.no\0"
+"steigen.no\0"
+"local\0akrehamn.no\0"
+"!pref.chiba.jp\0info.pk\0"
+"info.pl\0""6bone.pl\0"
+"klepp.no\0kherson.ua\0"
+"ketrzyn.pl\0info.pr\0"
+"sweden.museum\0"
+"lardal.no\0"
+"!retina.ar\0gz.cn\0"
+"barletta-trani-andria.it\0vikna.no\0"
+"bearalv\xc3\xa1hki.no\0"
+"broker.aero\0gov.nc.tr\0"
+"info.na\0k12.fl.us\0"
+"hembygdsforbund.museum\0"
+"entertainment.aero\0jerusalem.museum\0l\xc3\xa6rdal.no\0"
+"hitra.no\0sogndal.no\0"
+"farmequipment.museum\0info.mv\0info.nf\0\xc3\xa5lg\xc3\xa5rd.no\0"
+"la-spezia.it\0"
+"skanland.no\0fam.pk\0"
+"skole.museum\0"
+"art.museum\0"
+"presidio.museum\0"
+"3.bg\0public.museum\0"
+"h\xc3\xb8yanger.no\0zagan.pl\0"
+"an.it\0"
+"philadelphia.museum\0info.nr\0"
+"pesarourbino.it\0g\xc3\xa1ivuotna.no\0"
+"poltava.ua\0"
+"nt.ro\0"
+"station.museum\0"
+"mi.th\0"
+"altoadige.it\0"
+"nu.it\0"
+"usculture.museum\0g.se\0"
+"h\xc3\xa1mm\xc3\xa1rfeasta.no\0"
+"daegu.kr\0info.la\0"
+"dovre.no\0"
+"ci.it\0horology.museum\0"
+"bergbau.museum\0"
+"press.museum\0"
+"gangwon.kr\0"
+"!city.kitakyushu.jp\0sor-varanger.no\0cc.hi.us\0"
+"fuossko.no\0"
+"zp.ua\0"
+"american.museum\0"
+"fl\xc3\xa5.no\0mi.us\0"
+"i.bg\0"
+"od.ua\0"
+"encyclopedic.museum\0"
+"ind.tn\0"
+"midatlantic.museum\0"
+"newyork.museum\0"
+"castres.museum\0"
+"act.edu.au\0"
+"topology.museum\0"
+"ed.jp\0"
+"of.by\0"
+"iris.arpa\0inf.br\0askim.no\0pyatigorsk.ru\0"
+"nord-fron.no\0nsn.us\0"
+"beardu.no\0"
+"agrar.hu\0corvette.museum\0chtr.k12.ma.us\0"
+"figueres.museum\0"
+"!pref.gunma.jp\0medizinhistorisches.museum\0"
+"tjeldsund.no\0"
+"nebraska.museum\0"
+"bellevue.museum\0"
+"abo.pa\0k12.al.us\0"
+"info.ki\0"
+"inf.cu\0sv.it\0"
+"jfk.museum\0"
+"!city.osaka.jp\0swinoujscie.pl\0"
+"bydgoszcz.pl\0"
+"!city.kyoto.jp\0"
+"uvic.museum\0"
+"madrid.museum\0steinkjer.no\0"
+"lib.ma.us\0"
+"sirdal.no\0"
+"n\xc3\xb8tter\xc3\xb8y.no\0"
+"taranto.it\0starnberg.museum\0"
+"vic.gov.au\0pvt.ge\0pors\xc3\xa1\xc5\x8bgu.no\0"
+"naroy.no\0ris\xc3\xb8r.no\0"
+"va.it\0salem.museum\0starachowice.pl\0"
+"!nawrastelecom.om\0"
+"town.museum\0te.ua\0"
+"se.net\0"
+"kemerovo.ru\0"
+"lerdal.no\0"
+"gs.va.no\0"
+"kms.ru\0"
+"consulado.st\0"
+"haram.no\0"
+"tysnes.no\0"
+"!pref.ibaraki.jp\0hamburg.museum\0"
+"\xc3\xa5rdal.no\0"
+"airline.aero\0"
+"crew.aero\0newhampshire.museum\0"
+"muenster.museum\0"
+"aerodrome.aero\0"
+"heroy.nordland.no\0belau.pw\0"
+"kamchatka.ru\0"
+"b\xc3\xa5""d\xc3\xa5""ddj\xc3\xa5.no\0lillehammer.no\0hi.us\0"
+"hk.cn\0"
+"!city.kobe.jp\0berlevag.no\0"
+"ardal.no\0"
+"askoy.no\0"
+"vardo.no\0"
+"fyresdal.no\0"
+"sassari.it\0"
+"video.hu\0drammen.no\0"
+"lyngen.no\0nakhodka.ru\0"
+"ip6.arpa\0games.hu\0"
+"online.museum\0"
+"k12.sd.us\0"
+"4.bg\0sebastopol.ua\0"
+"ao.it\0atlanta.museum\0"
+"lebork.pl\0"
+"ravenna.it\0"
+"railway.museum\0songdalen.no\0"
+"!pref.shimane.jp\0delaware.museum\0ed.pw\0"
+"f\xc3\xb8rde.no\0"
+"living.museum\0"
+"juif.museum\0"
+"lomza.pl\0"
+"h.se\0"
+"!bl.uk\0"
+"portland.museum\0\xe7\xb5\x84\xe7\xb9\x94.tw\0"
+"stj\xc3\xb8rdal.no\0"
+"lecce.it\0"
+"bz.it\0"
+"farmstead.museum\0va.no\0"
+"express.aero\0!nacion.ar\0"
+"presse.km\0gs.of.no\0"
+"\xe5\x8f\xb0\xe7\x81\xa3\0"
+"og.ao\0gyeongbuk.kr\0vestv\xc3\xa5g\xc3\xb8y.no\0"
+"prd.fr\0"
+"pp.ru\0pp.se\0"
+"forum.hu\0!pref.saga.jp\0"
+"kvalsund.no\0"
+"!city.kawasaki.jp\0n\xc3\xa5\xc3\xa5mesjevuemie.no\0"
+"j.bg\0"
+"vlaanderen.museum\0"
+"cc.va.us\0"
+"\xd8\xa7\xd9\x8a\xd8\xb1\xd8\xa7\xd9\x86.ir\0alabama.museum\0"
+"school.museum\0her\xc3\xb8y.m\xc3\xb8re-og-romsdal.no\0"
+"\xc3\xa5seral.no\0"
+"traniandriabarletta.it\0"
+"flog.br\0"
+"presse.ml\0"
+"k\xc3\xa1r\xc3\xa1\xc5\xa1johka.no\0"
+"historisch.museum\0"
+"farm.museum\0palmsprings.museum\0oslo.no\0dyroy.no\0stranda.no\0"
+"gs.rl.no\0r\xc3\xa5""de.no\0"
+"bomlo.no\0s\xc3\xb8rum.no\0"
+"jan-mayen.no\0ivgu.no\0"
+"coop\0"
+"agr.br\0k12.ak.us\0"
+"!nic.ar\0catanzaro.it\0fusa.no\0"
+"hu.com\0"
+"inf.mk\0"
+"vet.br\0"
+"k12.mt.us\0k12.nd.us\0"
+"vlog.br\0\xe5\x85\xac\xe5\x8f\xb8.cn\0sandnessjoen.no\0"
+"lib.az.us\0"
+"nsw.edu.au\0of.no\0\xc3\xb8stre-toten.no\0"
+"*.okinawa.jp\0"
+"vb.it\0"
+"asso.fr\0firenze.it\0"
+"trieste.it\0"
+"\xe5\x85\xac\xe5\x8f\xb8.hk\0"
+"museet.museum\0"
+"prd.km\0"
+"navuotna.no\0lib.ca.us\0"
+"cc.nv.us\0"
+"asso.gp\0"
+"meraker.no\0"
+"h\xc3\xa1pmir.no\0"
+"i.ph\0"
+"sx.cn\0jeonnam.kr\0"
+"halden.no\0"
+"fed.us\0"
+"medio-campidano.it\0tsk.ru\0"
+"barcelona.museum\0"
+"giessen.museum\0roma.museum\0"
+"hl.cn\0"
+"\xe0\xae\x87\xe0\xae\xb2\xe0\xae\x99\xe0\xaf\x8d\xe0\xae\x95\xe0\xaf\x88\0"
+"biz.bb\0benevento.it\0rl.no\0bygland.no\0"
+"port.fr\0asso.ht\0prd.mg\0"
+"biz.at\0"
+"tra.kp\0"
+"*.aichi.jp\0khabarovsk.ru\0"
+"campidano-medio.it\0"
+"biz.az\0"
+"newmexico.museum\0va.us\0"
+"finearts.museum\0"
+"murmansk.ru\0"
+"\xc3\xb8rsta.no\0radom.pl\0k12.sc.us\0"
+"5.bg\0kvinesdal.no\0"
+"ap.it\0"
+"*.fukushima.jp\0"
+"asso.bj\0"
+"mad.museum\0"
+"lebesby.no\0"
+"og.it\0glas.museum\0sauda.no\0"
+"i.se\0"
+"k12.tx.us\0"
+"asso.ci\0mk.ua\0"
+"cesena-forli.it\0"
+"lowicz.pl\0"
+"k12.id.us\0"
+"tas.gov.au\0"
+"lukow.pl\0"
+"utazas.hu\0"
+"maritimo.museum\0bjark\xc3\xb8y.no\0"
+"adm.br\0"
+"pr.it\0lib.vi.us\0"
+"bergamo.it\0k12.va.us\0"
+"k.bg\0"
+"railroad.museum\0"
+"!british-library.uk\0"
+"cincinnati.museum\0"
+"sorreisa.no\0"
+"asso.dz\0!nel.uk\0"
+"rm.it\0"
+"nv.us\0"
+"nx.cn\0gos.pk\0"
+"vic.edu.au\0"
+"biella.it\0tjome.no\0"
+"r\xc3\xb8yken.no\0"
+"beiarn.no\0"
+"qc.ca\0"
+"georgia.museum\0square.museum\0"
+"labor.museum\0omasvuotna.no\0cc.la.us\0"
+"br.com\0reggioemilia.it\0"
+"kristiansund.no\0"
+"sorum.no\0"
+"orsta.no\0"
+"furniture.museum\0surrey.museum\0eng.pro\0"
+"asn.lv\0balat.no\0"
+"lavangen.no\0sld.pa\0"
+"fla.no\0k12.ms.us\0k12.nc.us\0"
+"bardu.no\0"
+"donostia.museum\0"
+"club.tw\0"
+"elburg.museum\0"
+"gs.hl.no\0lodingen.no\0"
+"samara.ru\0"
+"vc.it\0*.nagasaki.jp\0"
+"fosnes.no\0"
+"fuel.aero\0"
+"qc.com\0"
+"skjervoy.no\0"
+"bill.museum\0kv\xc3\xa6""fjord.no\0"
+"skydiving.aero\0*.tokushima.jp\0"
+"!congresodelalengua3.ar\0laquila.it\0k12.ct.us\0"
+"gorge.museum\0linz.museum\0sherbrooke.museum\0"
+"tranoy.no\0ing.pa\0"
+"ptz.ru\0"
+"kr.it\0prato.it\0stat.no\0"
+"\xd0\xb8\xd0\xba\xd0\xbe\xd0\xbc.museum\0"
+"cosenza.it\0"
+"stj\xc3\xb8rdalshalsen.no\0"
+"finland.museum\0leka.no\0cc.pr.us\0"
+"historichouses.museum\0s\xc3\xa1l\xc3\xa1t.no\0"
+"venice.it\0"
+"biz.ki\0"
+"g\xc3\xa1ls\xc3\xa1.no\0"
+"\xe7\xbb\x84\xe7\xbb\x87.hk\0"
+"*.yamanashi.jp\0"
+"rad\xc3\xb8y.no\0"
+"6.bg\0"
+"fareast.ru\0"
+"paragliding.aero\0ba.it\0aq.it\0"
+"sk\xc3\xa5nland.no\0"
+"its.me\0"
+"us.na\0"
+"hl.no\0cc.ga.us\0"
+"ac\0granvin.no\0"
+"ad\0qld.edu.au\0!city.sapporo.jp\0"
+"ae\0"
+"af\0"
+"ag\0crotone.it\0"
+"dallas.museum\0"
+"ai\0brussels.museum\0"
+"dali.museum\0"
+"la.us\0"
+"al\0salzburg.museum\0"
+"am\0"
+"an\0cl.it\0"
+"ao\0"
+"aq\0ba\0"
+"bb\0"
+"as\0lajolla.museum\0"
+"at\0"
+"be\0"
+"bf\0inderoy.no\0snz.ru\0"
+"aw\0bg\0"
+"ax\0bh\0cim.br\0ltd.gi\0biz.mv\0"
+"bi\0xz.cn\0\xe7\xb5\x84\xe7\xb9\x94.hk\0biz.mw\0"
+"az\0bj\0"
+"bm\0tranibarlettaandria.it\0naamesjevuemie.no\0"
+"chattanooga.museum\0"
+"bo\0"
+"l.bg\0"
+"ca\0"
+"br\0stateofdelaware.museum\0"
+"bs\0cc\0"
+"cd\0biz.nr\0"
+"cf\0berlev\xc3\xa5g.no\0"
+"bw\0cg\0snaase.no\0"
+"ch\0harvestcelebration.museum\0ck.ua\0"
+"by\0ci\0"
+"bz\0bahccavuotna.no\0"
+"cl\0yuzhno-sakhalinsk.ru\0"
+"cm\0halsa.no\0lyngdal.no\0"
+"cn\0"
+"co\0rn.it\0childrens.museum\0frankfurt.museum\0"
+"cr\0"
+"pskov.ru\0"
+"cu\0de\0"
+"cv\0fr.it\0lib.ky.us\0"
+"aseral.no\0kvam.no\0"
+"cx\0hellas.museum\0"
+"hof.no\0"
+"cz\0dj\0k12.la.us\0"
+"dk\0moscow.museum\0"
+"sosnowiec.pl\0"
+"dm\0biz.pk\0"
+"schokoladen.museum\0biz.pl\0"
+"far.br\0arna.no\0tynset.no\0"
+"even\xc3\xa1\xc5\xa1\xc5\xa1i.no\0"
+"ec\0"
+"biz.pr\0"
+"ee\0celtic.museum\0"
+"scientist.aero\0modern.museum\0"
+"pr.us\0"
+"dz\0"
+"mj\xc3\xb8ndalen.no\0s\xc3\xb8r-odal.no\0"
+"!nic.tr\0"
+"conference.aero\0vestnes.no\0k12.mn.us\0"
+"!pref.hiroshima.jp\0"
+"es\0trapani.it\0"
+"fermo.it\0vard\xc3\xb8.no\0"
+"eu\0gs.hm.no\0r\xc3\xb8""d\xc3\xb8y.no\0stordal.no\0"
+"gc.ca\0!nhs.uk\0"
+"jgora.pl\0"
+"fi\0stjordal.no\0"
+"fm\0!mediaphone.om\0"
+"kirov.ru\0pvt.k12.ma.us\0"
+"fo\0"
+"ga\0hyllestad.no\0"
+"gov.ac\0fr\0andriabarlettatrani.it\0ga.us\0"
+"gov.ae\0gd\0estate.museum\0"
+"gov.af\0ge\0tolga.no\0"
+"gf\0asso.re\0cc.oh.us\0"
+"gg\0florida.museum\0"
+"presse.ci\0gh\0"
+"gi\0k12.dc.us\0"
+"ltd.lk\0orland.no\0"
+"gov.al\0"
+"gl\0tokke.no\0"
+"hanggliding.aero\0gm\0"
+"hareid.no\0"
+"gov.ba\0tj.cn\0gp\0"
+"gov.bb\0gq\0"
+"gov.as\0gr\0agrigento.it\0lc.it\0"
+"gs\0kalmykia.ru\0aero.tt\0"
+"gov.bf\0"
+"county.museum\0"
+"gov.bh\0hn.cn\0gw\0"
+"gov.az\0gy\0assn.lk\0guernsey.museum\0"
+"hk\0"
+"gov.bm\0h\xc3\xa6gebostad.no\0biz.tj\0"
+"hm\0computer.museum\0"
+"gov.bo\0hn\0kl\xc3\xa6""bu.no\0"
+"pulawy.pl\0"
+"gov.br\0"
+"trd.br\0gov.bs\0hr\0reggio-calabria.it\0historyofscience.museum\0lipetsk.ru\0"
+"gov.cd\0*.nagoya.jp\0"
+"ht\0id\0spjelkavik.no\0"
+"hu\0ie\0aero.mv\0"
+"marketplace.aero\0mn.it\0biz.tt\0"
+"gov.by\0saintlouis.museum\0mer\xc3\xa5ker.no\0"
+"gov.bz\0"
+"7.bg\0gov.cl\0virtual.museum\0"
+"gov.cm\0vennesla.no\0kr.ua\0"
+"gov.cn\0im\0ar.it\0galsa.no\0rovno.ua\0"
+"gov.co\0in\0"
+"io\0limanowa.pl\0"
+"iq\0k12.ga.us\0"
+"ir\0"
+"riik.ee\0is\0\xc3\xa1laheadju.no\0"
+"gov.cu\0it\0hawaii.museum\0seaport.museum\0"
+"je\0pubol.museum\0hm.no\0"
+"gov.cx\0"
+"*.chiba.jp\0"
+"*.kawasaki.jp\0"
+"k.se\0"
+"gov.dm\0"
+"aland.fi\0vik.no\0"
+"yk.ca\0jo\0kobierzyce.pl\0"
+"jp\0biz.vn\0"
+"presse.fr\0lib.il.us\0\xe9\xa6\x99\xe6\xb8\xaf\0"
+"gov.ec\0"
+"transport.museum\0bronnoy.no\0"
+"slg.br\0gov.ee\0asso.nc\0bievat.no\0"
+"nyny.museum\0"
+"kg\0"
+"mo-i-rana.no\0"
+"gov.dz\0ki\0"
+"monmouth.museum\0"
+"suldal.no\0"
+"bc.ca\0km\0zt.ua\0"
+"pt.it\0kn\0"
+"fineart.museum\0"
+"la\0"
+"kr\0gulen.no\0"
+"m.bg\0mo.cn\0lc\0alaheadju.no\0g\xc3\xa1\xc5\x8bgaviika.no\0"
+"nowaruda.pl\0cc.ut.us\0"
+"br\xc3\xb8nn\xc3\xb8y.no\0"
+"ky\0li\0overhalla.no\0"
+"kz\0khv.ru\0"
+"lk\0"
+"artdeco.museum\0"
+"ma\0fortworth.museum\0kostroma.ru\0"
+"ro.it\0kirkenes.no\0vestby.no\0"
+"urbino-pesaro.it\0ls\0mc\0alstahaug.no\0"
+"blog.br\0gov.ge\0lt\0md\0"
+"lu\0me\0botanicgarden.museum\0"
+"gov.gg\0lv\0oh.us\0"
+"gov.gh\0mg\0valley.museum\0"
+"gov.gi\0mh\0"
+"ly\0sandiego.museum\0"
+"mk\0"
+"ml\0"
+"gov.gn\0rollag.no\0naklo.pl\0"
+"mn\0"
+"mo\0"
+"mp\0leirvik.no\0"
+"gov.gr\0mq\0na\0cc.ks.us\0"
+"mr\0"
+"ms\0nc\0"
+"valer.hedmark.no\0"
+"mu\0ne\0"
+"mv\0nf\0"
+"mw\0"
+"mx\0nord-odal.no\0jur.pro\0"
+"my\0"
+"gov.hk\0name.hr\0"
+"nl\0"
+"astronomy.museum\0lib.nm.us\0"
+"catania.it\0"
+"no\0"
+"skjerv\xc3\xb8y.no\0"
+"k12.ne.us\0"
+"monza-e-della-brianza.it\0!pref.fukushima.jp\0nr\0"
+"gov.ie\0"
+"stuttgart.museum\0nu\0cc.mn.us\0"
+"karasjohka.no\0"
+"engine.aero\0bearalvahki.no\0"
+"oyer.no\0"
+"ve.it\0"
+"gov.im\0froland.no\0cc.ar.us\0"
+"gov.in\0magadan.ru\0"
+"pescara.it\0"
+"gov.iq\0usdecorativearts.museum\0"
+"gov.ir\0pa\0"
+"gov.is\0"
+"gov.it\0lavagis.no\0"
+"gov.je\0"
+"naustdal.no\0pe\0k12.or.us\0"
+"gd.cn\0carraramassa.it\0pf\0"
+"ph\0"
+"cc.ny.us\0"
+"rissa.no\0"
+"info\0pk\0pomorze.pl\0"
+"pl\0"
+"gov.jo\0asso.km\0pn\0"
+"*.okayama.jp\0cieszyn.pl\0"
+"freight.aero\0"
+"pr\0"
+"narvik.no\0ps\0"
+"!pref.aichi.jp\0elverum.no\0pt\0"
+"edunet.tn\0"
+"gov.kg\0"
+"flatanger.no\0marker.no\0pw\0"
+"gov.ki\0nuremberg.museum\0"
+"aip.ee\0"
+"gov.km\0"
+"gov.kn\0"
+"gov.kp\0"
+"rieti.it\0gov.la\0bajddar.no\0"
+"gov.lb\0aviation.museum\0"
+"gov.lc\0"
+"asso.mc\0"
+"re\0"
+"ut.us\0"
+"sa.gov.au\0gov.ky\0"
+"mo.it\0gov.kz\0"
+"gov.lk\0"
+"iraq.museum\0"
+"badajoz.museum\0"
+"8.bg\0inder\xc3\xb8y.no\0"
+"monticello.museum\0ro\0ks.ua\0"
+"gov.ma\0svizzera.museum\0"
+"gov.lr\0sa\0"
+"matera.it\0sb\0"
+"gov.lt\0rs\0sc\0"
+"gov.me\0sd\0"
+"gov.lv\0ru\0se\0"
+"gov.mg\0"
+"rw\0sg\0"
+"gov.ly\0assisi.museum\0kids.museum\0sh\0"
+"si\0"
+"gov.mk\0"
+"gov.ml\0sk\0"
+"sl\0"
+"gov.mn\0airguard.museum\0sm\0"
+"gov.mo\0l.se\0sn\0"
+"so\0"
+"gov.mr\0ks.us\0"
+"name.az\0sr\0"
+"naturhistorisches.museum\0tc\0"
+"trainer.aero\0cn.it\0urbinopesaro.it\0gov.mu\0nativeamerican.museum\0st\0td\0"
+"gov.mv\0su\0"
+"trentino.it\0gov.mw\0gov.ng\0tf\0"
+"tg\0"
+"co.ae\0venezia.it\0gov.my\0th\0"
+"!pref.ehime.jp\0sy\0"
+"co.ag\0lewismiller.museum\0ostrowiec.pl\0sz\0tj\0"
+"tk\0"
+"motorcycle.museum\0tl\0"
+"birdart.museum\0trogstad.no\0tm\0"
+"tn\0"
+"humanities.museum\0to\0"
+"pu.it\0gov.nr\0ua\0lib.ut.us\0"
+"co.ao\0"
+"co.ba\0trondheim.no\0tt\0"
+"in-addr.arpa\0tempioolbia.it\0!city.yokohama.jp\0mn.us\0"
+"n.bg\0schoenbrunn.museum\0tv\0"
+"co.at\0aremark.no\0tw\0ug\0"
+"jus.br\0"
+"co.bi\0bialowieza.pl\0ar.us\0"
+"audnedaln.no\0kustanai.ru\0"
+"va\0"
+"us\0vc\0"
+"newport.museum\0"
+"kopervik.no\0gov.ph\0vg\0"
+"ny.us\0vi\0"
+"co.bw\0finn\xc3\xb8y.no\0gov.pk\0uz\0"
+"honefoss.no\0gov.pl\0lanbib.se\0"
+"co.ci\0"
+"gov.pn\0intl.tn\0"
+"act.gov.au\0vn\0"
+"television.museum\0gov.pr\0"
+"sykkylven.no\0v\xc3\xa5ler.hedmark.no\0gov.ps\0"
+"gov.pt\0"
+"co.cr\0vu\0"
+"legnica.pl\0"
+"sa.au\0"
+"bjarkoy.no\0"
+"openair.museum\0birkenes.no\0lib.nj.us\0"
+"fylkesbibl.no\0holt\xc3\xa5len.no\0"
+"iz.hr\0"
+"ws\0"
+"oceanographique.museum\0"
+"b\xc3\xa1id\xc3\xa1r.no\0cc.mo.us\0"
+"\xc3\xb8ygarden.no\0"
+"contemporary.museum\0"
+"gb.com\0cc.as.us\0"
+"belluno.it\0gov.sa\0"
+"gov.sb\0"
+"gov.rs\0gov.sc\0"
+"gov.sd\0"
+"!pref.nagasaki.jp\0gov.ru\0"
+"asia\0"
+"sa.cr\0gov.rw\0gov.sg\0"
+"kuzbass.ru\0"
+"gs.vf.no\0"
+"gov.sl\0"
+"norfolk.museum\0"
+"k12.de.us\0"
+"mil\0"
+"rendalen.no\0"
+"gov.st\0"
+"agro.pl\0"
+"orkdal.no\0"
+"le.it\0gov.sy\0"
+"gov.tj\0"
+"co.gg\0nore-og-uvdal.no\0v\xc3\xa5ler.\xc3\xb8stfold.no\0"
+"gov.tl\0"
+"gov.tn\0"
+"gov.to\0"
+"kids.us\0"
+"equipment.aero\0gov.ua\0"
+"!city.niigata.jp\0gov.tt\0"
+"sel.no\0"
+"l\xc3\xa4ns.museum\0"
+"gov.tw\0"
+"rennebu.no\0"
+"egersund.no\0"
+"medecin.km\0"
+"co.gy\0"
+"!mecon.ar\0"
+"berlin.museum\0"
+"carrara-massa.it\0"
+"9.bg\0"
+"pri.ee\0gov.vc\0"
+"at.it\0"
+"muosat.no\0"
+"co.id\0"
+"co.hu\0"
+"etne.no\0"
+"\xc3\xa1lt\xc3\xa1.no\0"
+"gov.vn\0"
+"modelling.aero\0"
+"co.im\0"
+"co.in\0\xc3\xa5krehamn.no\0m.se\0"
+"gouv.fr\0*.kitakyushu.jp\0"
+"narviika.no\0"
+"rennes\xc3\xb8y.no\0"
+"co.ir\0afjord.no\0"
+"lea\xc5\x8bgaviika.no\0buryatia.ru\0"
+"co.it\0coastaldefence.museum\0"
+"co.je\0vf.no\0"
+"osteroy.no\0"
+"uslivinghistory.museum\0"
+"aerobatic.aero\0"
+"mesaverde.museum\0mining.museum\0"
+"a\xc3\xa9roport.ci\0gov.ws\0"
+"co.jp\0copenhagen.museum\0"
+"pv.it\0"
+"r\xc3\xb8mskog.no\0"
+"vossevangen.no\0porsanger.no\0"
+"salat.no\0mo.us\0"
+"o.bg\0imperia.it\0carrier.museum\0"
+"carbonia-iglesias.it\0"
+"as.us\0"
+"alvdal.no\0"
+"state.museum\0mandal.no\0cn.ua\0"
+"cuneo.it\0"
+"gouv.ht\0"
+"!city.okayama.jp\0co.kr\0"
+"co.lc\0"
+"sa.it\0"
+"donna.no\0"
+"sortland.no\0"
+"tomsk.ru\0"
+"birthplace.museum\0l\xc3\xb8""dingen.no\0"
+"ge.it\0orenburg.ru\0"
+"cn.com\0"
+"co.ma\0"
+"co.ls\0skaun.no\0name.vn\0"
+"navigation.aero\0"
+"cagliari.it\0co.me\0portal.museum\0"
+"gouv.bj\0"
+"udine.it\0"
+"engineer.aero\0"
+"szczecin.pl\0"
+"wales.museum\0"
+"co.na\0bo.telemark.no\0"
+"austin.museum\0"
+"k12.mo.us\0"
+"co.mu\0"
+"gouv.ci\0"
+"co.mw\0"
+"esp.br\0"
+"naturalhistorymuseum.museum\0"
+"mosjoen.no\0"
+"solund.no\0"
+"name.tj\0"
+"sand\xc3\xb8y.no\0"
+"kunstunddesign.museum\0"
+"cartoonart.museum\0collection.museum\0gsm.pl\0"
+"aure.no\0"
+"!pref.yamaguchi.jp\0historical.museum\0"
+"name.tt\0"
+"england.museum\0valle.no\0"
+"cc.ok.us\0"
+"salangen.no\0"
+"gloppen.no\0"
+"cc.co.us\0"
+"contemporaryart.museum\0"
+"tas.edu.au\0"
+"trading.aero\0"
+"mazury.pl\0"
+"!pref.aomori.jp\0co.pl\0"
+"opoczno.pl\0"
+"*.kobe.jp\0co.pn\0"
+"oppegard.no\0"
+"co.pw\0"
+"saltdal.no\0smolensk.ru\0"
+"na.it\0\xc4\x8d\xc3\xa1hcesuolo.no\0"
+"vgs.no\0evenassi.no\0"
+"parachuting.aero\0jl.cn\0maritime.museum\0bd.se\0"
+"badaddja.no\0"
+"bergen.no\0"
+"brussel.museum\0"
+"avoues.fr\0"
+"cesenaforli.it\0"
+"oregontrail.museum\0"
+"ullensaker.no\0"
+"jobs\0"
+"accident-prevention.aero\0"
+"n.se\0"
+"association.museum\0california.museum\0"
+"cultural.museum\0co.rs\0"
+"zoology.museum\0"
+"pruszkow.pl\0"
+"control.aero\0nt.edu.au\0net\0komforb.se\0"
+"lincoln.museum\0aurland.no\0name.pr\0co.rw\0"
+"ostroleka.pl\0"
+"isernia.it\0"
+"tm.fr\0"
+"gs.ol.no\0"
+"nb.ca\0marnardal.no\0"
+"williamsburg.museum\0"
+"!jet.uk\0"
+"suisse.museum\0\xc3\xa5""fjord.no\0flakstad.no\0"
+"karmoy.no\0"
+"yn.cn\0chesapeakebay.museum\0"
+"nsw.au\0"
+"amur.ru\0co.st\0"
+"imb.br\0siellak.no\0\xe7\xb6\xb2\xe8\xb7\xaf.tw\0"
+"name.na\0"
+"co.th\0"
+"p.bg\0"
+"co.sz\0co.tj\0"
+"name.mv\0\xc3\xa5lesund.no\0lib.in.us\0"
+"lucerne.museum\0naumburg.museum\0"
+"society.museum\0name.my\0"
+"tinn.no\0"
+"co.tt\0"
+"unj\xc3\xa1rga.no\0"
+"co.ug\0"
+"lib.wy.us\0"
+"co.tz\0"
+"ass.km\0"
+"ok.us\0"
+"tm.hu\0kongsvinger.no\0"
+"ibestad.no\0"
+"juedisches.museum\0co.us\0"
+"cq.cn\0"
+"rs.ba\0"
+"wa.edu.au\0co.vi\0"
+"co.uz\0"
+"health.museum\0"
+"grue.no\0"
+"automotive.museum\0journalism.museum\0settlement.museum\0"
+"qh.cn\0interactive.museum\0"
+"snillfjord.no\0!national-library-scotland.uk\0"
+"balsfjord.no\0lib.nh.us\0"
+"kolobrzeg.pl\0"
+"gs.tm.no\0"
+"h\xc3\xb8nefoss.no\0"
+"ol.no\0"
+"music.museum\0moareke.no\0"
+"b\xc3\xb8.nordland.no\0"
+"name.mk\0lier.no\0"
+"eidfjord.no\0"
+"sc.cn\0tm.km\0"
+"jelenia-gora.pl\0sanok.pl\0"
+"intelligence.museum\0"
+"srv.br\0elblag.pl\0"
+"judygarland.museum\0"
+"padua.it\0"
+"k12.co.us\0"
+"lindesnes.no\0"
+"name.jo\0izhevsk.ru\0"
+"yorkshire.museum\0mel\xc3\xb8y.no\0"
+"tm.mc\0lib.pr.us\0"
+"hjartdal.no\0"
+"tm.mg\0"
+"bari.it\0milano.it\0"
+"lg.jp\0"
+"zgrad.ru\0"
+"sm\xc3\xb8la.no\0"
+"communications.museum\0"
+"arts.co\0seoul.kr\0engerdal.no\0"
+"oster\xc3\xb8y.no\0"
+"\xe6\x95\x8e\xe8\x82\xb2.hk\0foggia.it\0verran.no\0"
+"orskog.no\0voronezh.ru\0kv.ua\0"
+"av.it\0"
+"tm.no\0nissedal.no\0"
+"historisches.museum\0gs.mr.no\0"
+"medecin.fr\0"
+"montreal.museum\0"
+"o.se\0"
+"!metro.tokyo.jp\0sola.no\0"
+"k12.tn.us\0"
+"floro.no\0"
+"milan.it\0*.shiga.jp\0"
+"berkeley.museum\0"
+"maintenance.aero\0"
+"ws.na\0"
+"lindas.no\0cc.ia.us\0"
+"brescia.it\0embroidery.museum\0"
+"arezzo.it\0tm.pl\0"
+"r\xc3\xa6lingen.no\0"
+"burghof.museum\0"
+"rec.br\0"
+"q.bg\0"
+"!nawras.om\0"
+"hammarfeasta.no\0"
+"moss.no\0"
+"on.ca\0"
+"gouv.rw\0"
+"luxembourg.museum\0"
+"rec.co\0british.museum\0"
+"reggio-emilia.it\0"
+"gouv.sn\0lib.wv.us\0"
+"avocat.fr\0"
+"simbirsk.ru\0"
+"jar.ru\0"
+"monza-brianza.it\0"
+"tm.ro\0"
+"imageandsound.museum\0"
+"jpn.com\0mr.no\0"
+"siracusa.it\0"
+"norilsk.ru\0tm.se\0"
+"tn.it\0"
+"jeju.kr\0"
+"!pref.fukuoka.jp\0"
+"*.hyogo.jp\0portlligat.museum\0"
+"!pref.osaka.jp\0"
+"siena.it\0sc.kr\0omaha.museum\0saskatchewan.museum\0"
+"phoenix.museum\0vanylven.no\0"
+"botanicalgarden.museum\0"
+"turek.pl\0"
+"vagsoy.no\0"
+"riodejaneiro.museum\0"
+"vi.it\0"
+"uy.com\0"
+"kristiansand.no\0"
+"sd.cn\0trento.it\0"
+"muncie.museum\0"
+"berg.no\0meldal.no\0"
+"nes.buskerud.no\0"
+"saratov.ru\0"
+"gs.oslo.no\0"
+"harstad.no\0vaga.no\0"
+"research.museum\0"
+"brunel.museum\0ia.us\0"
+"test.tj\0"
+"columbia.museum\0"
+"ms.it\0stockholm.museum\0"
+"reklam.hu\0"
+"pomorskie.pl\0lg.ua\0"
+"bg.it\0historicalsociety.museum\0rns.tn\0"
+"mallorca.museum\0surgut.ru\0cc.sc.us\0"
+"ushistory.museum\0"
+"palana.ru\0"
+"snoasa.no\0"
+"naturalsciences.museum\0"
+"yaroslavl.ru\0"
+"unjarga.no\0"
+"p.se\0"
+"ingatlan.hu\0"
+"irc.pl\0"
+"savona.it\0"
+"cr.it\0"
+"test.ru\0cc.tn.us\0"
+"ms.kr\0museumvereniging.museum\0"
+"time.no\0k12.ia.us\0"
+"vladimir.ru\0"
+"correios-e-telecomunica\xc3\xa7\xc3\xb5""es.museum\0"
+"gouv.km\0nationalfirearms.museum\0"
+"m\xc3\xa1latvuopmi.no\0"
+"aero\0yosemite.museum\0"
+"r.bg\0school.na\0"
+"cc.vi.us\0"
+"*.wakayama.jp\0"
+"beauxarts.museum\0averoy.no\0ullensvang.no\0bar.pro\0"
+"!city.hiroshima.jp\0"
+"b\xc3\xa1hccavuotna.no\0"
+"frosta.no\0"
+"gdynia.pl\0"
+"medical.museum\0"
+"embaixada.st\0"
+"balsan.it\0vantaa.museum\0"
+"za.net\0"
+"!city.saitama.jp\0lib.ks.us\0"
+"fnd.br\0"
+"ru.com\0se.com\0hol.no\0modalen.no\0"
+"gouv.ml\0chukotka.ru\0"
+"malopolska.pl\0"
+"mansion.museum\0"
+"iki.fi\0children.museum\0"
+"cyber.museum\0rec.nf\0mo\xc3\xa5reke.no\0"
+"to.it\0"
+"hasvik.no\0"
+"\xc3\xb8yer.no\0"
+"arts.ro\0sc.ug\0"
+"lib.ar.us\0"
+"sc.tz\0cc.ms.us\0cc.nc.us\0"
+"etc.br\0poznan.pl\0"
+"cnt.br\0viking.museum\0"
+"*.miyazaki.jp\0"
+"melhus.no\0"
+"skodje.no\0vevelstad.no\0"
+"sc.us\0"
+"upow.gov.pl\0"
+"!city.fukuoka.jp\0brandywinevalley.museum\0natuurwetenschappen.museum\0tranby.no\0"
+"bahn.museum\0msk.ru\0"
+"delmenhorst.museum\0"
+"russia.museum\0fuoisku.no\0"
+"shell.museum\0"
+"r\xc3\xa1isa.no\0"
+"hs.kr\0udmurtia.ru\0"
+"palermo.it\0"
+"pilot.aero\0"
+"tn.us\0"
+"priv.hu\0"
+"li.it\0"
+"kr\xc3\xa5""anghke.no\0mosreg.ru\0"
+"lib.fl.us\0"
+"plants.museum\0"
+"ulsan.kr\0national.museum\0"
+"mil.ac\0!pref.nara.jp\0surgeonshall.museum\0"
+"mil.ae\0santacruz.museum\0vi.us\0"
+"wlocl.pl\0"
+"mt.it\0napoli.it\0alaska.museum\0arts.nf\0"
+"missoula.museum\0"
+"rec.ro\0"
+"mil.al\0"
+"marburg.museum\0waw.pl\0"
+"pharmaciens.km\0indianapolis.museum\0larsson.museum\0"
+"cc.sd.us\0"
+"mil.ba\0mobi\0"
+"indianmarket.museum\0"
+"recreation.aero\0padova.it\0"
+"varese.it\0parti.se\0"
+"mil.az\0"
+"mil.bo\0!pref.kagoshima.jp\0khmelnitskiy.ua\0"
+"rygge.no\0"
+"os\xc3\xb8yro.no\0"
+"mil.br\0"
+"cs.it\0"
+"austevoll.no\0fjell.no\0"
+"mil.by\0"
+"!pref.tokushima.jp\0org\0"
+"mil.cn\0gs.svalbard.no\0"
+"mil.co\0"
+"pz.it\0lib.va.us\0\xd1\x80\xd1\x84\0"
+"\xe4\xb8\xaa\xe4\xba\xba.hk\0ms.us\0nc.us\0k12.wi.us\0"
+"s.bg\0drangedal.no\0"
+"en.it\0"
+"culturalcenter.museum\0"
+"house.museum\0divttasvuotna.no\0"
+"fhs.no\0"
+"circus.museum\0"
+"priv.at\0"
+"mil.ec\0"
+"ruovat.no\0"
+"midsund.no\0vagan.no\0"
+"casadelamoneda.museum\0"
+"bristol.museum\0"
+"and.museum\0"
+"ascolipiceno.it\0computerhistory.museum\0vyatka.ru\0"
+"uhren.museum\0"
+"lahppi.no\0"
+"*.yokohama.jp\0cody.museum\0lib.al.us\0"
+"colonialwilliamsburg.museum\0indian.museum\0cc.ky.us\0"
+"tp.it\0biev\xc3\xa1t.no\0"
+"can.br\0royken.no\0"
+"id.ir\0"
+"mediocampidano.it\0tromso.no\0"
+"kartuzy.pl\0k12.ok.us\0"
+"*.saitama.jp\0stjohn.museum\0m\xc3\xa1tta-v\xc3\xa1rjjat.no\0"
+"mil.ge\0trani-barletta-andria.it\0"
+"lib.as.us\0"
+"swiebodzin.pl\0cc.mt.us\0cc.nd.us\0"
+"mil.gh\0"
+"science-fiction.museum\0\xd9\x82\xd8\xb7\xd8\xb1\0"
+"airtraffic.aero\0"
+"konskowola.pl\0"
+"scienceandhistory.museum\0nysa.pl\0sd.us\0"
+"balestrand.no\0"
+"oygarden.no\0"
+"her\xc3\xb8y.nordland.no\0"
+"!pref.ishikawa.jp\0strand.no\0"
+"\xe7\xb5\x84\xe7\xbb\x87.hk\0mil.hn\0"
+"gob.bo\0volda.no\0"
+"losangeles.museum\0larvik.no\0"
+"university.museum\0"
+"cc.dc.us\0"
+"mil.id\0"
+"sorfold.no\0"
+"watch-and-clock.museum\0"
+"flor\xc3\xb8.no\0"
+"nittedal.no\0oppeg\xc3\xa5rd.no\0"
+"k12.ri.us\0"
+"gob.cl\0"
+"komi.ru\0"
+"government.aero\0mil.in\0"
+"mil.iq\0id.lv\0"
+"culture.museum\0"
+"id.ly\0"
+"raholt.no\0"
+"lubin.pl\0grozny.ru\0"
+"kchr.ru\0"
+"nikolaev.ua\0"
+"lib.sd.us\0"
+"de.com\0"
+"mil.jo\0"
+"*.kanagawa.jp\0gaular.no\0miasta.pl\0"
+"bi.it\0rnu.tn\0uzhgorod.ua\0"
+"idrett.no\0v\xc3\xa5gs\xc3\xb8y.no\0"
+"wroclaw.pl\0"
+"res.aero\0ne.jp\0mil.kg\0"
+"\xc3\xa5mli.no\0"
+"education.museum\0"
+"dgca.aero\0"
+"mil.km\0"
+"trolley.museum\0"
+"cci.fr\0r.se\0"
+"archaeological.museum\0"
+"monzaedellabrianza.it\0mil.kr\0"
+"gob.es\0kvafjord.no\0ky.us\0"
+"lecco.it\0"
+"ct.it\0"
+"magazine.aero\0"
+"operaunite.com\0ne.kr\0"
+"mil.kz\0skoczow.pl\0"
+"nf.ca\0"
+"western.museum\0"
+"kunst.museum\0gaivuotna.no\0karpacz.pl\0spb.ru\0cc.id.us\0"
+"slask.pl\0"
+"youth.museum\0"
+"adv.br\0campidanomedio.it\0!songfest.om\0"
+"geelvinck.museum\0\xd8\xa7\xd9\x85\xd8\xa7\xd8\xb1\xd8\xa7\xd8\xaa\0"
+"mil.lv\0"
+"fie.ee\0mil.mg\0mt.us\0nd.us\0k12.vt.us\0"
+"t.bg\0ushuaia.museum\0"
+"off.ai\0"
+"irkutsk.ru\0"
+"stor-elvdal.no\0tourism.tn\0"
+"penza.ru\0"
+"bj.cn\0\xe4\xb8\xad\xe5\x9b\xbd\0"
+"civilwar.museum\0mil.mv\0opole.pl\0"
+"nes.akershus.no\0"
+"mil.my\0karelia.ru\0"
+"como.it\0sande.vestfold.no\0"
+"\xe4\xb8\xad\xe5\x9c\x8b\0"
+"gob.hn\0lib.la.us\0"
+"mil.no\0cc.wv.us\0"
+"boleslawiec.pl\0"
+"!pref.niigata.jp\0gs.sf.no\0dc.us\0k12.mi.us\0"
+"museum\0dep.no\0kv\xc3\xa6nangen.no\0l\xc3\xa1hppi.no\0"
+"film.museum\0"
+"frei.no\0"
+"notodden.no\0risor.no\0"
+"messina.it\0"
+"eidsberg.no\0"
+"krakow.pl\0lib.mt.us\0lib.nd.us\0"
+"rauma.no\0"
+"mulhouse.museum\0"
+"sibenik.museum\0grong.no\0mil.pe\0"
+"budejju.no\0k12.nv.us\0"
+"stavanger.no\0mil.ph\0"
+"forli-cesena.it\0"
+"naples.it\0cc.ne.us\0"
+"s\xc3\xb8r-aurdal.no\0"
+"mil.pl\0"
+"vibo-valentia.it\0ski.museum\0siedlce.pl\0"
+"bus.museum\0"
+"tozsde.hu\0"
+"!pref.shizuoka.jp\0santabarbara.museum\0"
+"zhitomir.ua\0"
+"pro.az\0"
+"ne.pw\0"
+"pro.br\0orkanger.no\0b\xc3\xb8.telemark.no\0"
+"roma.it\0cc.ct.us\0"
+"heritage.museum\0giske.no\0"
+"!pref.kumamoto.jp\0prof.pr\0"
+"*.kochi.jp\0"
+"andria-barletta-trani.it\0*.toyama.jp\0sveio.no\0"
+"id.us\0"
+"bolt.hu\0"
+"fetsund.no\0porsgrunn.no\0"
+"iglesias-carbonia.it\0"
+"sf.no\0"
+"mil.ru\0"
+"from.hr\0asnes.no\0mil.rw\0"
+"alesund.no\0sos.pl\0"
+"livorno.it\0"
+"crafts.museum\0"
+"aquila.it\0"
+"vega.no\0"
+"jewelry.museum\0"
+"sk\xc3\xa1nit.no\0chita.ru\0"
+"pro.ec\0"
+"fortmissoula.museum\0j\xc3\xb8lster.no\0"
+"pro\0mil.st\0"
+"busan.kr\0lib.ga.us\0"
+"dellogliastra.it\0"
+"aosta.it\0chungnam.kr\0gob.mx\0"
+"mil.sy\0k12.hi.us\0"
+"mil.tj\0"
+"ulan-ude.ru\0mil.to\0wv.us\0"
+"luster.no\0volgograd.ru\0"
+"pa.it\0kommunalforbund.se\0lib.tx.us\0"
+"s.se\0"
+"qsl.br\0"
+"mil.tw\0"
+"est.pr\0ens.tn\0"
+"lib.id.us\0"
+"mil.tz\0"
+"uscountryestate.museum\0"
+"agents.aero\0"
+"\xc3\xb8vre-eiker.no\0ne.ug\0"
+"pb.ao\0"
+"gob.pa\0ne.tz\0"
+"tur.br\0"
+"mil.vc\0"
+"or.at\0gob.pe\0"
+"s\xc3\xb8r-fron.no\0"
+"or.bi\0ne.us\0"
+"u.bg\0gob.pk\0"
+"stavern.no\0"
+"brindisi.it\0"
+"aknoluokta.no\0"
+"!pref.kyoto.jp\0tydal.no\0"
+"plc.ly\0muos\xc3\xa1t.no\0"
+"or.ci\0hamaroy.no\0priv.pl\0"
+"vestre-slidre.no\0gniezno.pl\0"
+"\xe7\xae\x87\xe4\xba\xba.hk\0"
+"andebu.no\0"
+"nieruchomosci.pl\0\xd8\xa7\xd9\x84\xd8\xb3\xd8\xb9\xd9\x88\xd8\xaf\xd9\x8a\xd8\xa9\0"
+"or.cr\0pro.ht\0bolzano.it\0"
+"ct.us\0k12.md.us\0"
+"za.org\0"
+"!icnet.uk\0"
+"localhistory.museum\0"
+"firm.ht\0"
+"lel.br\0tr.it\0kvanangen.no\0"
+"sondre-land.no\0t\xc3\xb8nsberg.no\0vefsn.no\0"
+"nature.museum\0yamal.ru\0"
+"rv.ua\0"
+"lans.museum\0lib.ne.us\0"
+"lur\xc3\xb8y.no\0"
+"eu.com\0firm.in\0"
+"hjelmeland.no\0"
+"gs.tr.no\0"
+"casino.hu\0essex.museum\0tourism.pl\0"
+"rennesoy.no\0"
+"priv.no\0"
+"baths.museum\0mytis.ru\0"
+"tingvoll.no\0"
+"cc.az.us\0"
+"sh.cn\0"
+"!pref.miyazaki.jp\0s\xc3\xb8rfold.no\0"
+"aurskog-holand.no\0malatvuopmi.no\0"
+"lib.ct.us\0"
+"cc.pa.us\0"
+"pa.gov.pl\0"
+"firm.co\0cc.de.us\0"
+"nrw.museum\0"
+"daejeon.kr\0livinghistory.museum\0"
+"gildeskal.no\0lund.no\0"
+"\xc3\xb8ksnes.no\0stavropol.ru\0"
+"b\xc3\xa6rum.no\0r\xc3\xb8yrvik.no\0"
+"osoyro.no\0"
+"priv.me\0sula.no\0!parliament.uk\0"
+"nationalheritage.museum\0"
+"jaworzno.pl\0"
+"dinosaur.museum\0"
+"garden.museum\0trust.museum\0"
+"turen.tn\0"
+"kautokeino.no\0"
+"pro.na\0"
+"gorizia.it\0"
+"siljan.no\0"
+"or.id\0pro.mv\0"
+"bieszczady.pl\0www.ro\0"
+"lib.ee\0antiques.museum\0brasil.museum\0tr.no\0"
+"aejrie.no\0"
+"!pref.hokkaido.jp\0"
+"schlesisches.museum\0"
+"huissier-justice.fr\0or.it\0"
+"t.se\0"
+"environment.museum\0"
+"vindafjord.no\0"
+"edu.ac\0or.jp\0"
+"tree.museum\0"
+"groundhandling.aero\0edu.af\0"
+"rochester.museum\0sanfrancisco.museum\0"
+"ebiz.tw\0"
+"kirovograd.ua\0"
+"edu.al\0"
+"edu.an\0\xc3\xa1k\xc5\x8boluokta.no\0v\xc3\xa5g\xc3\xa5.no\0"
+"v.bg\0"
+"edu.ba\0"
+"edu.bb\0nesset.no\0"
+"hornindal.no\0pro.pr\0"
+"or.kr\0"
+"az.us\0"
+"edu.bh\0volkenkunde.museum\0"
+"edu.bi\0"
+"edu.az\0"
+"b\xc3\xb8mlo.no\0"
+"edu.bm\0"
+"edu.bo\0tyumen.ru\0"
+"edu.br\0"
+"edu.bs\0pa.us\0"
+"alto-adige.it\0whaling.museum\0"
+"*.iwate.jp\0"
+"edu.ci\0law.pro\0"
+"edu.bz\0de.us\0"
+"lib.ak.us\0"
+"edu.cn\0"
+"edu.co\0"
+"laspezia.it\0"
+"baidar.no\0"
+"ts.it\0"
+"or.na\0"
+"edu.cu\0hotel.lk\0"
+"show.aero\0or.mu\0"
+"sandnes.no\0"
+"museumcenter.museum\0"
+"edu.dm\0kazan.ru\0"
+"biz\0caltanissetta.it\0odessa.ua\0k12.oh.us\0"
+"crimea.ua\0"
+"research.aero\0lom.no\0"
+"edu.ec\0florence.it\0clock.museum\0sshn.se\0"
+"edu.ee\0game.tw\0"
+"!pref.okinawa.jp\0"
+"ilawa.pl\0"
+"edu.dz\0indiana.museum\0"
+"gs.jan-mayen.no\0"
+"publ.pt\0"
+"nom.ad\0"
+"skanit.no\0gdansk.pl\0k12.pa.us\0"
+"nom.ag\0edu.es\0"
+"if.ua\0"
+"pro.tt\0lib.de.us\0"
+"environmentalconservation.museum\0cc.or.us\0"
+"bern.museum\0nat.tn\0"
+"rubtsovsk.ru\0"
+"!educ.ar\0masoy.no\0"
+"bologna.it\0"
+"\xc3\xa5snes.no\0fhv.se\0"
+"*.tottori.jp\0radoy.no\0"
+"romskog.no\0"
+"malbork.pl\0"
+"olbiatempio.it\0"
+"edu.ge\0"
+"edu.gh\0"
+"edu.gi\0"
+"or.pw\0"
+"hob\xc3\xb8l.no\0"
+"nom.br\0edu.gn\0virginia.museum\0mbone.pl\0!nls.uk\0"
+"seljord.no\0pro.vn\0"
+"edu.gp\0"
+"edu.gr\0"
+"!uba.ar\0!pref.saitama.jp\0"
+"greta.fr\0gs.aa.no\0kvinnherad.no\0"
+"lib.sc.us\0"
+"js.cn\0nom.co\0edu.hk\0"
+"lesja.no\0"
+"bl.it\0"
+"edu.hn\0\xc3\xb8ystre-slidre.no\0mari-el.ru\0"
+"hotel.hu\0"
+"rindal.no\0"
+"edu.ht\0"
+"!pref.miyagi.jp\0"
+"midtre-gauldal.no\0"
+"xj.cn\0australia.museum\0"
+"ab.ca\0salvadordali.museum\0olawa.pl\0"
+"pc.it\0"
+"u.se\0"
+"edu.in\0b\xc3\xa1l\xc3\xa1t.no\0"
+"ln.cn\0alta.no\0"
+"chelyabinsk.ru\0"
+"edu.iq\0"
+"ontario.museum\0"
+"edu.is\0"
+"edu.it\0"
+"b\xc3\xa5tsfjord.no\0"
+"trysil.no\0or.th\0"
+"utsira.no\0"
+"nom.es\0edu.jo\0fhsk.se\0"
+"bale.museum\0"
+"w.bg\0"
+"lillesand.no\0"
+"edu.kg\0"
+"amusement.aero\0"
+"edu.ki\0"
+"fauske.no\0or.ug\0"
+"int.az\0askvoll.no\0eidskog.no\0cv.ua\0"
+"algard.no\0"
+"edu.km\0or.tz\0"
+"nom.fr\0edu.kn\0"
+"*.ibaraki.jp\0hoylandet.no\0"
+"int.bo\0edu.kp\0"
+"edu.la\0"
+"si.it\0edu.lb\0travel.pl\0"
+"edu.lc\0mx.na\0n\xc3\xa1vuotna.no\0ovre-eiker.no\0"
+"aa.no\0!siemens.om\0"
+"sciences.museum\0or.us\0"
+"cat\0"
+"edu.ky\0"
+"int.ci\0edu.kz\0firm.ro\0cc.wy.us\0"
+"edu.lk\0vaapste.no\0"
+"!pref.tochigi.jp\0"
+"int.co\0podlasie.pl\0"
+"edu.lr\0"
+"karikatur.museum\0jamal.ru\0"
+"gjovik.no\0krager\xc3\xb8.no\0k12.az.us\0"
+"edu.me\0"
+"ud.it\0edu.lv\0entomology.museum\0"
+"edu.mg\0moskenes.no\0"
+"\xe6\x94\xbf\xe5\xba\x9c.hk\0edu.ly\0"
+"stpetersburg.museum\0"
+"edu.mk\0"
+"edu.ml\0nordreisa.no\0"
+"!pref.fukui.jp\0lib.ms.us\0lib.nc.us\0"
+"edu.mn\0\xd9\x81\xd9\x84\xd8\xb3\xd8\xb7\xd9\x8a\xd9\x86\0"
+"fot.br\0edu.mo\0"
+"iron.museum\0"
+"asti.it\0annefrank.museum\0stv.ru\0cc.nh.us\0"
+"edu.mv\0"
+"lodi.it\0edu.mw\0edu.ng\0"
+"gwangju.kr\0edu.mx\0"
+"edu.my\0"
+"soundandvision.museum\0"
+"lenvik.no\0"
+"ballooning.aero\0"
+"name\0"
+"jogasz.hu\0frogn.no\0"
+"history.museum\0"
+"consultant.aero\0edu.nr\0"
+"manchester.museum\0"
+"*.hiroshima.jp\0"
+"pol.dz\0"
+"*.tochigi.jp\0heimatunduhren.museum\0"
+"!pref.kanagawa.jp\0"
+"firm.nf\0edu.pa\0"
+"coop.ht\0pc.pl\0"
+"chicago.museum\0"
+"vn.ua\0"
+"edu.pe\0"
+"tana.no\0edu.pf\0"
+"edu.ph\0"
+"nom.km\0"
+"travel.tt\0"
+"edu.pk\0"
+"experts-comptables.fr\0edu.pl\0bryansk.ru\0"
+"edu.pn\0"
+"evje-og-hornnes.no\0warszawa.pl\0"
+"ac.ae\0"
+"edu.pr\0"
+"vaksdal.no\0edu.ps\0dni.us\0"
+"po.gov.pl\0edu.pt\0"
+"nordre-land.no\0vadso.no\0"
+"rnrt.tn\0"
+"sport.hu\0!pref.gifu.jp\0voss.no\0targi.pl\0"
+"flesberg.no\0"
+"photography.museum\0"
+"modena.it\0tonsberg.no\0"
+"ac.at\0"
+"ac.be\0coop.br\0"
+"services.aero\0"
+"nom.mg\0"
+"wielun.pl\0"
+"jefferson.museum\0wy.us\0"
+"pd.it\0ot.it\0neues.museum\0slattum.no\0"
+"vdonsk.ru\0"
+"ar.com\0edu.sa\0"
+"\xc3\xa5l.no\0edu.sb\0"
+"edu.rs\0edu.sc\0"
+"ac.ci\0int.is\0edu.sd\0!tsk.tr\0"
+"br\xc3\xb8nn\xc3\xb8ysund.no\0and\xc3\xb8y.no\0edu.ru\0"
+"pol.ht\0"
+"edu.rw\0edu.sg\0"
+"gyeongnam.kr\0olecko.pl\0"
+"ac.cn\0"
+"graz.museum\0"
+"coldwar.museum\0edu.sl\0"
+"ac.cr\0"
+"edu.sn\0"
+"hamar.no\0"
+"histoire.museum\0"
+"!city.shizuoka.jp\0"
+"edu.st\0"
+"oceanographic.museum\0nh.us\0"
+"x.bg\0"
+"surnadal.no\0"
+"fc.it\0costume.museum\0stalowa-wola.pl\0"
+"valer.ostfold.no\0edu.sy\0"
+"edu.tj\0"
+"arq.br\0"
+"aeroclub.aero\0odo.br\0pe.ca\0\xe7\xb6\xb2\xe7\xb5\xa1.cn\0bronnoysund.no\0nom.pa\0"
+"edu.to\0"
+"paleo.museum\0nom.pe\0edu.ua\0"
+"int.la\0trustee.museum\0forsand.no\0krasnoyarsk.ru\0"
+"!pref.hyogo.jp\0"
+"edu.tt\0"
+"zarow.pl\0"
+"edu.tw\0"
+"nom.pl\0"
+"community.museum\0kvitsoy.no\0"
+"int.lk\0tychy.pl\0"
+"k12.me.us\0"
+"jondal.no\0edu.vc\0"
+"illustration.museum\0"
+"clinton.museum\0"
+"tas.au\0es.kr\0"
+"production.aero\0"
+"rodoy.no\0"
+"database.museum\0bodo.no\0"
+"anthro.museum\0landes.museum\0edu.vn\0"
+"nom.re\0"
+"altai.ru\0"
+"filatelia.museum\0"
+"sk.ca\0lezajsk.pl\0"
+"rockart.museum\0int.mv\0"
+"int.mw\0herad.no\0"
+"eti.br\0ac.gn\0"
+"fedje.no\0nom.ro\0"
+"money.museum\0"
+"\xd9\x85\xd8\xb5\xd8\xb1\0"
+"horten.no\0"
+"gangaviika.no\0mielec.pl\0"
+"uw.gov.pl\0"
+"moma.museum\0"
+"edu.ws\0"
+"go.ci\0"
+"tv.bo\0technology.museum\0"
+"s\xc3\xb8ndre-land.no\0"
+"tv.br\0"
+"jor.br\0lib.dc.us\0"
+"arboretum.museum\0"
+"go.cr\0"
+"artsandcrafts.museum\0\xd8\xaa\xd9\x88\xd9\x86\xd8\xb3\0"
+"psc.br\0ac.id\0!city.chiba.jp\0"
+"wa.au\0"
+"rome.it\0"
+"amli.no\0"
+"ac.im\0lo.it\0"
+"ac.in\0"
+"\xe7\xb6\xb2\xe7\xb5\xa1.hk\0durham.museum\0"
+"ac.ir\0"
+"torino.museum\0"
+"loabat.no\0"
+"com\0"
+"nalchik.ru\0"
+"yakutia.ru\0"
+"settlers.museum\0"
+"!promocion.ar\0int.pt\0"
+"union.aero\0"
+"utah.museum\0"
+"giehtavuoatna.no\0"
+"ac.jp\0"
+"air-traffic-control.aero\0"
+"silk.museum\0usantiques.museum\0"
+"bn.it\0"
+"kalisz.pl\0"
+"perm.ru\0"
+"aoste.it\0bindal.no\0"
+"coloradoplateau.museum\0k12.gu.us\0"
+"frosinone.it\0forde.no\0"
+"epilepsy.museum\0"
+"olbia-tempio.it\0"
+"journalist.aero\0ac.kr\0*.sch.uk\0"
+"nic.im\0sciencesnaturelles.museum\0bedzin.pl\0"
+"nic.in\0pe.it\0"
+"w.se\0"
+"!pref.okayama.jp\0"
+"urn.arpa\0"
+"cinema.museum\0"
+"monza.it\0versailles.museum\0int.ru\0"
+"andasuolo.no\0skj\xc3\xa5k.no\0chernovtsy.ua\0"
+"nyc.museum\0int.rw\0paroch.k12.ma.us\0"
+"ringerike.no\0"
+"ac.ma\0"
+"org.ac\0civilaviation.aero\0"
+"rakkestad.no\0"
+"org.ae\0ac.me\0"
+"org.af\0"
+"org.ag\0"
+"org.ai\0stokke.no\0"
+"airport.aero\0"
+"finnoy.no\0"
+"org.al\0"
+"org.an\0y.bg\0habmer.no\0"
+"stadt.museum\0holtalen.no\0"
+"int.tj\0"
+"org.ba\0gjerdrum.no\0"
+"org.bb\0ascoli-piceno.it\0molde.no\0r\xc3\xb8st.no\0tysfjord.no\0"
+"pe.kr\0rybnik.pl\0"
+"go.id\0"
+"ac.mu\0"
+"ac.mw\0ac.ng\0"
+"org.bh\0\xc3\xa5mot.no\0rana.no\0"
+"org.bi\0"
+"org.az\0belgorod.ru\0int.tt\0"
+"ae.org\0"
+"group.aero\0posts-and-telecommunications.museum\0"
+"org.bm\0salerno.it\0"
+"etnedal.no\0"
+"org.bo\0*.hokkaido.jp\0donetsk.ua\0"
+"ostroda.pl\0"
+"org.br\0"
+"org.bs\0"
+"go.it\0h\xc3\xb8ylandet.no\0"
+"zgorzelec.pl\0"
+"org.bw\0"
+"org.ci\0"
+"org.bz\0vicenza.it\0resistance.museum\0"
+"missile.museum\0"
+"org.cn\0"
+"org.co\0assassination.museum\0"
+"go.jp\0"
+"tv.it\0austrheim.no\0ac.pa\0"
+"verbania.it\0"
+"palace.museum\0"
+"tmp.br\0int.vn\0"
+"org.cu\0"
+"paris.museum\0"
+"media.aero\0hokksund.no\0"
+"arts.museum\0gemological.museum\0hammerfest.no\0"
+"k12.ny.us\0"
+"org.dm\0hemsedal.no\0ringsaker.no\0sklep.pl\0"
+"h\xc3\xa5.no\0cc.nj.us\0"
+"rzeszow.pl\0"
+"go.kr\0gjesdal.no\0ac.pr\0"
+"org.ec\0"
+"org.ee\0"
+"media.museum\0"
+"terni.it\0touch.museum\0zakopane.pl\0"
+"journal.aero\0org.dz\0"
+"incheon.kr\0"
+"b\xc3\xa1hcavuotna.no\0"
+"leksvik.no\0ulvik.no\0"
+"plantation.museum\0"
+"org.es\0loyalist.museum\0"
+"gildesk\xc3\xa5l.no\0bytom.pl\0"
+"bo.nordland.no\0"
+"ambulance.aero\0iglesiascarbonia.it\0"
+"tw.cn\0\xe6\x96\xb0\xe5\x8a\xa0\xe5\x9d\xa1\0"
+"chocolate.museum\0"
+"pittsburgh.museum\0"
+"royrvik.no\0sor-odal.no\0ac.rs\0"
+"kaluga.ru\0"
+"org.ge\0erotica.hu\0ac.ru\0ac.se\0"
+"org.gg\0leangaviika.no\0ac.rw\0"
+"org.gh\0v\xc3\xa6r\xc3\xb8y.no\0"
+"org.gi\0"
+"jevnaker.no\0"
+"org.gn\0tv.na\0leikanger.no\0"
+"org.gp\0"
+"ask\xc3\xb8y.no\0"
+"org.gr\0wroc.pl\0"
+"ad.jp\0"
+"powiat.pl\0"
+"tj\xc3\xb8me.no\0"
+"coop.tt\0"
+"ac.th\0"
+"mragowo.pl\0ac.sz\0ac.tj\0"
+"org.hk\0bo.it\0"
+"philately.museum\0"
+"org.hn\0"
+"fet.no\0"
+"axis.museum\0mansions.museum\0"
+"wiki.br\0"
+"org.ht\0"
+"org.hu\0piacenza.it\0scotland.museum\0cpa.pro\0"
+"ac.ug\0"
+"coop.mv\0x.se\0"
+"coop.mw\0ac.tz\0"
+"bmd.br\0"
+"org.im\0ralingen.no\0"
+"org.in\0"
+"cz.it\0lib.ia.us\0"
+"org.iq\0"
+"org.ir\0"
+"org.is\0"
+"nl.ca\0"
+"org.je\0"
+"childrensgarden.museum\0"
+"kvits\xc3\xb8y.no\0go.pw\0"
+"sokndal.no\0"
+"ra.it\0grimstad.no\0"
+"denmark.museum\0"
+"ac.vn\0"
+"ecn.br\0org.jo\0"
+"bialystok.pl\0nj.us\0"
+"z.bg\0bilbao.museum\0stargard.pl\0nic.tj\0"
+"eisenbahn.museum\0"
+"fe.it\0bryne.no\0vrn.ru\0"
+"cc.wa.us\0"
+"sex.hu\0skierva.no\0"
+"org.kg\0"
+"org.ki\0"
+"org.km\0"
+"org.kn\0khakassia.ru\0"
+"org.kp\0"
+"org.la\0"
+"org.lb\0"
+"org.lc\0"
+"francaise.museum\0"
+"panama.museum\0"
+"rotorcraft.aero\0gateway.museum\0olkusz.pl\0"
+"org.ky\0czeladz.pl\0ryazan.ru\0"
+"org.kz\0"
+"org.lk\0dyr\xc3\xb8y.no\0"
+"raisa.no\0"
+"dlugoleka.pl\0"
+"org.ma\0"
+"org.lr\0prochowice.pl\0"
+"org.ls\0"
+"org.me\0sandoy.no\0s\xc3\xb8r-varanger.no\0"
+"org.lv\0"
+"org.mg\0"
+"tel\0go.th\0"
+"org.ly\0"
+"steam.museum\0go.tj\0"
+"org.mk\0pasadena.museum\0jessheim.no\0lib.mn.us\0"
+"org.ml\0"
+"software.aero\0"
+"org.mn\0"
+"org.mo\0"
+"*.fukui.jp\0decorativearts.museum\0"
+"spy.museum\0org.na\0jorpeland.no\0"
+"vads\xc3\xb8.no\0"
+"org.mu\0building.museum\0gausdal.no\0"
+"org.mv\0nannestad.no\0"
+"org.mw\0org.ng\0go.ug\0"
+"vr.it\0org.mx\0"
+"org.my\0"
+"go.tz\0"
+"oppdal.no\0"
+"uk.net\0"
+"coop.km\0"
+"*.kyoto.jp\0"
+"sarpsborg.no\0org.nr\0"
+"chernigov.ua\0"
+"ha.cn\0no.com\0"
+"space.museum\0"
+"org.pa\0"
+"*.ar\0"
+"usgarden.museum\0"
+"*.bd\0org.pe\0"
+"*.au\0org.pf\0um.gov.pl\0"
+"bio.br\0"
+"org.ph\0"
+"org.pk\0"
+"fr\xc3\xa6na.no\0org.pl\0"
+"nord-aurdal.no\0org.pn\0"
+"*.bn\0handson.museum\0agrinet.tn\0"
+"kviteseid.no\0"
+"rel.ht\0virtuel.museum\0atm.pl\0org.pr\0"
+"org.ps\0cherkassy.ua\0"
+"org.pt\0wa.us\0"
+"*.bt\0arendal.no\0magnitka.ru\0"
+"depot.museum\0porsangu.no\0"
+"laakesvuemie.no\0"
+"sor-fron.no\0"
+"heroy.more-og-romsdal.no\0"
+"*.ck\0"
+"!rakpetroleum.om\0"
+"kr\xc3\xb8""dsherad.no\0mail.pl\0"
+"mod.gi\0"
+"gs.nl.no\0"
+"mb.ca\0"
+"pavia.it\0"
+"civilisation.museum\0folldal.no\0"
+"suli.hu\0"
+"brumunddal.no\0"
+"*.cy\0"
+"pg.it\0troms\xc3\xb8.no\0"
+"sex.pl\0y.se\0"
+"org.ro\0"
+"*.do\0"
+"caserta.it\0org.sa\0"
+"za.com\0halloffame.museum\0org.sb\0lviv.ua\0"
+"mill.museum\0org.rs\0org.sc\0"
+"org.sd\0"
+"idv.hk\0!omanmobile.om\0org.ru\0org.se\0"
+"langev\xc3\xa5g.no\0r\xc3\xa5holt.no\0starostwo.gov.pl\0"
+"trani-andria-barletta.it\0org.sg\0"
+"*.eg\0hvaler.no\0"
+"*.ehime.jp\0"
+"gmina.pl\0"
+"bod\xc3\xb8.no\0org.sl\0"
+"edu\0org.sn\0"
+"org.so\0lib.wi.us\0"
+"kommune.no\0"
+"nome.pt\0"
+"*.er\0namdalseid.no\0k12.wa.us\0"
+"nm.cn\0org.st\0"
+"*.et\0d\xc3\xb8nna.no\0"
+"jewish.museum\0preservation.museum\0"
+"slupsk.pl\0org.sy\0"
+"art.br\0org.sz\0org.tj\0"
+"ntr.br\0*.fj\0ski.no\0"
+"*.fk\0rimini.it\0grajewo.pl\0"
+"loppa.no\0"
+"franziskaner.museum\0notteroy.no\0org.tn\0"
+"org.to\0"
+"nesoddtangen.no\0"
+"org.ua\0"
+"discovery.museum\0wloclawek.pl\0"
+"lakas.hu\0org.tt\0"
+"kurgan.ru\0"
+"baltimore.museum\0nkz.ru\0org.tw\0"
+"com.ac\0castle.museum\0"
+"*.fukuoka.jp\0sandefjord.no\0varggat.no\0"
+"com.af\0"
+"com.ag\0"
+"ato.br\0k12.nj.us\0"
+"com.ai\0"
+"city.hu\0oryol.ru\0"
+"com.al\0nl.no\0mielno.pl\0cc.ma.us\0"
+"org.vc\0"
+"com.an\0g12.br\0"
+"*.gt\0"
+"*.gu\0"
+"com.ba\0"
+"com.bb\0americanart.museum\0"
+"org.vi\0"
+"kunstsammlung.museum\0"
+"com.aw\0"
+"flight.aero\0com.bh\0lib.mo.us\0org.vn\0"
+"com.bi\0adygeya.ru\0"
+"com.az\0"
+"art.dz\0"
+"com.bm\0"
+"dr\xc3\xb8""bak.no\0"
+"com.bo\0isla.pr\0"
+"com.br\0"
+"com.bs\0ustka.pl\0kuban.ru\0"
+"press.aero\0"
+"vs.it\0"
+"meloy.no\0"
+"*.il\0ulm.museum\0"
+"com.by\0com.ci\0genoa.it\0"
+"com.bz\0sn.cn\0"
+"lib.or.us\0"
+"santafe.museum\0org.ws\0"
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKCOOKIEJARTLD_P_H
diff --git a/src/network/access/qnetworkcookiejartlds_p.h.INFO b/src/network/access/qnetworkcookiejartlds_p.h.INFO
new file mode 100644
index 0000000000..57a8d0e0cc
--- /dev/null
+++ b/src/network/access/qnetworkcookiejartlds_p.h.INFO
@@ -0,0 +1,17 @@
+The file qnetworkcookiejartlds_p.h is generated from the Public Suffix
+List (see [1] and [2]), by the program residing at
+util/network/cookiejar-generateTLDs in the Qt source tree.
+
+That program generates a character array and an index array from the
+list to provide fast lookups of elements within C++.
+
+Those arrays in qnetworkcookiejartlds_p.h are derived from the Public
+Suffix List ([2]), which was originally provided by
+Jo Hermans <jo.hermans@gmail.com>.
+
+The file qnetworkcookiejartlds_p.h was last generated Friday,
+November 19th 15:24 2010.
+
+----
+[1] list: http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1
+[2] homepage: http://publicsuffix.org/
diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp
new file mode 100644
index 0000000000..271494d008
--- /dev/null
+++ b/src/network/access/qnetworkdiskcache.cpp
@@ -0,0 +1,728 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//#define QNETWORKDISKCACHE_DEBUG
+
+
+#include "qnetworkdiskcache.h"
+#include "qnetworkdiskcache_p.h"
+#include "QtCore/qscopedpointer.h"
+
+#include <qfile.h>
+#include <qdir.h>
+#include <qdatetime.h>
+#include <qdiriterator.h>
+#include <qurl.h>
+#include <qcryptographichash.h>
+#include <qdebug.h>
+
+#define CACHE_POSTFIX QLatin1String(".d")
+#define PREPARED_SLASH QLatin1String("prepared/")
+#define CACHE_VERSION 7
+#define DATA_DIR QLatin1String("data")
+
+#define MAX_COMPRESSION_SIZE (1024 * 1024 * 3)
+
+#ifndef QT_NO_NETWORKDISKCACHE
+
+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.
+
+ A network disk cache can be enabled by:
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 0
+
+ When sending requests, to control the preference of when to use the cache
+ and when to use the network, consider the following:
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 1
+
+ To check whether the response came from the cache or from the network, the
+ following can be applied:
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 2
+*/
+
+/*!
+ 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('/');
+
+ d->dataDirectory = d->cacheDirectory + DATA_DIR + QString::number(CACHE_VERSION) + QLatin1Char('/');
+ d->prepareLayout();
+}
+
+/*!
+ \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;
+ }
+ }
+ QScopedPointer<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();
+ QT_TRY {
+ cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
+ } QT_CATCH(...) {
+ cacheItem->file = 0;
+ }
+ if (!cacheItem->file || !cacheItem->file->open()) {
+ qWarning() << "QNetworkDiskCache::prepare() unable to open temporary file";
+ cacheItem.reset();
+ return 0;
+ }
+ cacheItem->writeHeader(cacheItem->file);
+ device = cacheItem->file;
+ }
+ d->inserting[device] = cacheItem.take();
+ 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);
+}
+
+
+/*!
+ Create subdirectories and other housekeeping on the filesystem.
+ Prevents too many files from being present in any single directory.
+*/
+void QNetworkDiskCachePrivate::prepareLayout()
+{
+ QDir helper;
+ helper.mkpath(cacheDirectory + PREPARED_SLASH);
+
+ //Create directory and subdirectories 0-F
+ helper.mkpath(dataDirectory);
+ for (uint i = 0; i < 16 ; i++) {
+ QString str = QString::number(i, 16);
+ QString subdir = dataDirectory + str;
+ helper.mkdir(subdir);
+ }
+}
+
+
+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: couldn'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();
+ else
+ 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))
+ 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);
+ QScopedPointer<QBuffer> buffer;
+ if (!url.isValid())
+ return 0;
+ if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
+ buffer.reset(new QBuffer);
+ buffer->setData(d->lastItem.data.data());
+ } else {
+ QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
+ if (!file->open(QFile::ReadOnly | QIODevice::Unbuffered))
+ return 0;
+
+ if (!d->lastItem.read(file.data(), true)) {
+ file->close();
+ remove(url);
+ return 0;
+ }
+ if (d->lastItem.data.isOpen()) {
+ // compressed
+ buffer.reset(new QBuffer);
+ buffer->setData(d->lastItem.data.data());
+ } else {
+ buffer.reset(new QBuffer);
+ // ### verify that QFile uses the fd size and not the file name
+ qint64 size = file->size() - file->pos();
+ const uchar *p = 0;
+#if !defined(Q_OS_WINCE) && !defined(Q_OS_INTEGRITY)
+ p = file->map(file->pos(), size);
+#endif
+ if (p) {
+ buffer->setData((const char *)p, size);
+ file.take()->setParent(buffer.data());
+ } else {
+ buffer->setData(file->readAll());
+ }
+ }
+ }
+ buffer->open(QBuffer::ReadOnly);
+ return buffer.take();
+}
+
+/*!
+ \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 than 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;
+ }
+
+ // close file handle to prevent "in use" error when QFile::remove() is called
+ d->lastItem.reset();
+
+ QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
+ QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories);
+
+ QMultiMap<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)) {
+ cacheItems.insert(info.created(), path);
+ totalSize += info.size();
+ }
+ }
+
+ int removedFiles = 0;
+ qint64 goal = (maximumCacheSize() * 9) / 10;
+ QMultiMap<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
+ 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;
+}
+
+/*!
+ Given a URL, generates a unique enough filename (and subdirectory)
+ */
+QString QNetworkDiskCachePrivate::uniqueFileName(const QUrl &url)
+{
+ QUrl cleanUrl = url;
+ cleanUrl.setPassword(QString());
+ cleanUrl.setFragment(QString());
+
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(cleanUrl.toEncoded());
+ // convert sha1 to base36 form and return first 8 bytes for use as string
+ QByteArray id = QByteArray::number(*(qlonglong*)hash.result().data(), 36).left(8);
+ // generates <one-char subdir>/<8-char filname.d>
+ uint code = (uint)id.at(id.length()-1) % 16;
+ QString pathFragment = QString::number(code, 16) + QLatin1Char('/')
+ + QLatin1String(id) + CACHE_POSTFIX;
+
+ return pathFragment;
+}
+
+QString QNetworkDiskCachePrivate::tmpCacheFileName() const
+{
+ //The subdirectory is presumed to be already read for use.
+ return cacheDirectory + PREPARED_SLASH + QLatin1String("XXXXXX") + CACHE_POSTFIX;
+}
+
+/*!
+ Generates fully qualified path of cached resource from a URL.
+ */
+QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
+{
+ if (!url.isValid())
+ return QString();
+
+ QString fullpath = dataDirectory + uniqueFileName(url);
+ return fullpath;
+}
+
+/*!
+ 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 = CACHE_VERSION
+};
+
+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);
+ }
+
+ // quick and dirty check if metadata's URL field and the file's name are in synch
+ QString expectedFilename = QNetworkDiskCachePrivate::uniqueFileName(metaData.url());
+ if (!device->fileName().endsWith(expectedFilename))
+ return false;
+
+ return metaData.isValid();
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_NETWORKDISKCACHE
diff --git a/src/network/access/qnetworkdiskcache.h b/src/network/access/qnetworkdiskcache.h
new file mode 100644
index 0000000000..628f524b90
--- /dev/null
+++ b/src/network/access/qnetworkdiskcache.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKDISKCACHE_H
+#define QNETWORKDISKCACHE_H
+
+#include <QtNetwork/qabstractnetworkcache.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Network)
+
+#ifndef QT_NO_NETWORKDISKCACHE
+
+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)
+};
+
+#endif // QT_NO_NETWORKDISKCACHE
+
+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 0000000000..13db04fa1c
--- /dev/null
+++ b/src/network/access/qnetworkdiskcache_p.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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>
+
+#ifndef QT_NO_NETWORKDISKCACHE
+
+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)
+ {}
+
+ static QString uniqueFileName(const QUrl &url);
+ QString cacheFileName(const QUrl &url) const;
+ QString tmpCacheFileName() const;
+ bool removeFile(const QString &file);
+ void storeItem(QCacheItem *item);
+ void prepareLayout();
+ static quint32 crc32(const char *data, uint len);
+
+ mutable QCacheItem lastItem;
+ QString cacheDirectory;
+ QString dataDirectory;
+ qint64 maximumCacheSize;
+ qint64 currentCacheSize;
+
+ QHash<QIODevice*, QCacheItem*> inserting;
+ Q_DECLARE_PUBLIC(QNetworkDiskCache)
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_NETWORKDISKCACHE
+
+#endif // QNETWORKDISKCACHE_P_H
diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp
new file mode 100644
index 0000000000..c176dde669
--- /dev/null
+++ b/src/network/access/qnetworkreply.cpp
@@ -0,0 +1,795 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkreply.h"
+#include "qnetworkreply_p.h"
+#include <QtNetwork/qsslconfiguration.h>
+
+QT_BEGIN_NAMESPACE
+
+QNetworkReplyPrivate::QNetworkReplyPrivate()
+ : readBufferMaxSize(0),
+ operation(QNetworkAccessManager::UnknownOperation),
+ errorCode(QNetworkReply::NoError)
+ , isFinished(false)
+{
+ // 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
+ sent with QNetworkAccessManager
+
+ \reentrant
+ \ingroup network
+ \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.
+
+ \note Do not delete the object in the slot connected to the
+ error() or finished() signal. Use deleteLater().
+
+ \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 TemporaryNetworkFailureError the connection was broken due
+ to disconnection from the network, however the system has initiated
+ roaming to another access point. The request should be resubmitted
+ and will be processed as soon as the connection is re-established.
+
+ \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 ContentReSendError the request needed to be sent
+ again, but this failed for example because the upload data
+ could not be read a second time.
+
+ \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 unknown 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.
+
+ \note Do not delete the object in the slot connected to this
+ signal. Use deleteLater().
+
+ You can also use isFinished() to check if a QNetworkReply
+ has finished even before you receive the finished() signal.
+
+ \sa QNetworkAccessManager::finished(), isFinished()
+*/
+
+/*!
+ \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.
+
+ \note Do not delete the object in the slot connected to this
+ signal. Use deleteLater().
+
+ \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.
+
+ \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.
+
+ 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;
+}
+
+/*!
+ \since 4.6
+
+ Returns true when the reply has finished or was aborted.
+
+ \sa isRunning()
+*/
+bool QNetworkReply::isFinished() const
+{
+ return d_func()->isFinished;
+}
+
+/*!
+ \since 4.6
+
+ Returns true when the request is still processing and the
+ reply has not finished or was aborted yet.
+
+ \sa isFinished()
+*/
+bool QNetworkReply::isRunning() const
+{
+ return !isFinished();
+}
+
+/*!
+ 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();
+}
+
+/*! \typedef QNetworkReply::RawHeaderPair
+
+ RawHeaderPair is a QPair<QByteArray, QByteArray> where the first
+ QByteArray is the header name and the second is the header.
+ */
+
+/*!
+ Returns a list of raw header pairs.
+ */
+const QList<QNetworkReply::RawHeaderPair>& QNetworkReply::rawHeaderPairs() const
+{
+ Q_D(const QNetworkReply);
+ return d->rawHeaders;
+}
+
+/*!
+ 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);
+ }
+}
+
+/*!
+ \overload
+ \since 4.6
+
+ If this function is called, the SSL errors given in \a errors
+ will be ignored.
+
+ Note that you can set the expected certificate in the SSL error:
+ If, for instance, you want to issue a request to a server that uses
+ a self-signed certificate, consider the following snippet:
+
+ \snippet doc/src/snippets/code/src_network_access_qnetworkreply.cpp 0
+
+ Multiple calls to this function will replace the list of errors that
+ were passed in previous calls.
+ You can clear the list of errors you want to ignore by calling this
+ function with an empty list.
+
+ \sa sslConfiguration(), sslErrors(), QSslSocket::ignoreSslErrors()
+*/
+void QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
+{
+ // do this cryptic trick, because we could not add a virtual method to this class later on
+ // since that breaks binary compatibility
+ int id = metaObject()->indexOfMethod("ignoreSslErrorsImplementation(QList<QSslError>)");
+ if (id != -1) {
+ QList<QSslError> copy(errors);
+ 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(), QSslSocket::ignoreSslErrors()
+*/
+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
+}
+
+/*!
+ \since 4.8
+ Sets the reply as \a finished.
+
+ After having this set the replies data must not change.
+
+ \sa isFinished()
+*/
+void QNetworkReply::setFinished(bool finished)
+{
+ Q_D(QNetworkReply);
+ d->isFinished = finished;
+}
+
+
+/*!
+ 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 0000000000..3a511cc7a9
--- /dev/null
+++ b/src/network/access/qnetworkreply.h
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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,
+ TemporaryNetworkFailureError,
+ UnknownNetworkError = 99,
+
+ // proxy errors (101-199):
+ ProxyConnectionRefusedError = 101,
+ ProxyConnectionClosedError,
+ ProxyNotFoundError,
+ ProxyTimeoutError,
+ ProxyAuthenticationRequiredError,
+ UnknownProxyError = 199,
+
+ // content errors (201-299):
+ ContentAccessDenied = 201,
+ ContentOperationNotPermittedError,
+ ContentNotFoundError,
+ AuthenticationRequiredError,
+ ContentReSendError,
+ 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;
+ bool isFinished() const;
+ bool isRunning() 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;
+
+ typedef QPair<QByteArray, QByteArray> RawHeaderPair;
+ const QList<RawHeaderPair>& rawHeaderPairs() const;
+
+ // attributes
+ QVariant attribute(QNetworkRequest::Attribute code) const;
+
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration sslConfiguration() const;
+ void setSslConfiguration(const QSslConfiguration &configuration);
+ void ignoreSslErrors(const QList<QSslError> &errors);
+#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 setFinished(bool);
+ 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 0000000000..03cc6bd060
--- /dev/null
+++ b/src/network/access/qnetworkreply_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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;
+ bool isFinished;
+
+ 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/qnetworkreplydataimpl.cpp b/src/network/access/qnetworkreplydataimpl.cpp
new file mode 100644
index 0000000000..a09ff5c9ff
--- /dev/null
+++ b/src/network/access/qnetworkreplydataimpl.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkreplydataimpl_p.h"
+#include "private/qdataurl_p.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QMetaObject>
+
+QT_BEGIN_NAMESPACE
+
+QNetworkReplyDataImplPrivate::QNetworkReplyDataImplPrivate()
+ : QNetworkReplyPrivate()
+{
+}
+
+QNetworkReplyDataImplPrivate::~QNetworkReplyDataImplPrivate()
+{
+}
+
+QNetworkReplyDataImpl::~QNetworkReplyDataImpl()
+{
+}
+
+QNetworkReplyDataImpl::QNetworkReplyDataImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
+ : QNetworkReply(*new QNetworkReplyDataImplPrivate(), parent)
+{
+ Q_D(QNetworkReplyDataImpl);
+ setRequest(req);
+ setUrl(req.url());
+ setOperation(op);
+ setFinished(true);
+ QNetworkReply::open(QIODevice::ReadOnly);
+
+ QUrl url = req.url();
+
+ // FIXME qDecodeDataUrl should instead be rewritten to have the QByteArray
+ // and the mime type as an output parameter and return a bool instead
+ d->decodeDataUrlResult = qDecodeDataUrl(url);
+
+ if (! d->decodeDataUrlResult.first.isNull()) {
+ QString &mimeType = d->decodeDataUrlResult.first;
+ qint64 size = d->decodeDataUrlResult.second.size();
+ setHeader(QNetworkRequest::ContentTypeHeader, mimeType);
+ setHeader(QNetworkRequest::ContentLengthHeader, size);
+ QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
+
+ d->decodedData.setBuffer(&d->decodeDataUrlResult.second);
+ d->decodedData.open(QIODevice::ReadOnly);
+
+ QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
+ Q_ARG(qint64,size), Q_ARG(qint64, size));
+ QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ } else {
+ // something wrong with this URI
+ const QString msg = QCoreApplication::translate("QNetworkAccessDataBackend",
+ "Invalid URI: %1").arg(QString::fromLatin1(url.toEncoded()));
+ setError(QNetworkReply::ProtocolFailure, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolFailure));
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ }
+}
+
+void QNetworkReplyDataImpl::close()
+{
+ QNetworkReply::close();
+}
+
+void QNetworkReplyDataImpl::abort()
+{
+ QNetworkReply::close();
+}
+
+qint64 QNetworkReplyDataImpl::bytesAvailable() const
+{
+ Q_D(const QNetworkReplyDataImpl);
+ return QNetworkReply::bytesAvailable() + d->decodedData.bytesAvailable();
+}
+
+bool QNetworkReplyDataImpl::isSequential () const
+{
+ return true;
+}
+
+qint64 QNetworkReplyDataImpl::size() const
+{
+ Q_D(const QNetworkReplyDataImpl);
+ return d->decodedData.size();
+}
+
+/*!
+ \internal
+*/
+qint64 QNetworkReplyDataImpl::readData(char *data, qint64 maxlen)
+{
+ Q_D(QNetworkReplyDataImpl);
+
+ // TODO idea:
+ // Instead of decoding the whole data into new memory, we could decode on demand.
+ // Note that this might be tricky to do.
+
+ return d->decodedData.read(data, maxlen);
+}
+
+
+QT_END_NAMESPACE
+
+#include "moc_qnetworkreplydataimpl_p.cpp"
+
diff --git a/src/network/access/qnetworkreplydataimpl_p.h b/src/network/access/qnetworkreplydataimpl_p.h
new file mode 100644
index 0000000000..2048376b44
--- /dev/null
+++ b/src/network/access/qnetworkreplydataimpl_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKREPLYDATAIMPL_H
+#define QNETWORKREPLYDATAIMPL_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 <QBuffer>
+
+QT_BEGIN_NAMESPACE
+
+
+class QNetworkReplyDataImplPrivate;
+class QNetworkReplyDataImpl: public QNetworkReply
+{
+ Q_OBJECT
+public:
+ QNetworkReplyDataImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op);
+ ~QNetworkReplyDataImpl();
+ virtual void abort();
+
+ // reimplemented from QNetworkReply
+ virtual void close();
+ virtual qint64 bytesAvailable() const;
+ virtual bool isSequential () const;
+ qint64 size() const;
+
+ virtual qint64 readData(char *data, qint64 maxlen);
+
+ Q_DECLARE_PRIVATE(QNetworkReplyDataImpl)
+};
+
+class QNetworkReplyDataImplPrivate: public QNetworkReplyPrivate
+{
+public:
+ QNetworkReplyDataImplPrivate();
+ ~QNetworkReplyDataImplPrivate();
+
+ QPair<QString, QByteArray> decodeDataUrlResult;
+ QBuffer decodedData;
+
+ Q_DECLARE_PUBLIC(QNetworkReplyDataImpl)
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKREPLYDATAIMPL_H
diff --git a/src/network/access/qnetworkreplyfileimpl.cpp b/src/network/access/qnetworkreplyfileimpl.cpp
new file mode 100644
index 0000000000..babee32e08
--- /dev/null
+++ b/src/network/access/qnetworkreplyfileimpl.cpp
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qnetworkreplyfileimpl_p.h"
+
+#include "QtCore/qdatetime.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QFileInfo>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+QNetworkReplyFileImplPrivate::QNetworkReplyFileImplPrivate()
+ : QNetworkReplyPrivate(), realFileSize(0)
+{
+}
+
+QNetworkReplyFileImpl::~QNetworkReplyFileImpl()
+{
+}
+
+QNetworkReplyFileImpl::QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
+ : QNetworkReply(*new QNetworkReplyFileImplPrivate(), parent)
+{
+ setRequest(req);
+ setUrl(req.url());
+ setOperation(op);
+ setFinished(true);
+ QNetworkReply::open(QIODevice::ReadOnly);
+
+ QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
+
+ QUrl url = req.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
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Request for opening non-local file %1").arg(url.toString());
+ setError(QNetworkReply::ProtocolInvalidOperationError, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError));
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ return;
+ }
+#endif
+ if (url.path().isEmpty())
+ url.setPath(QLatin1String("/"));
+ setUrl(url);
+
+
+ QString fileName = url.toLocalFile();
+ if (fileName.isEmpty()) {
+ if (url.scheme() == QLatin1String("qrc"))
+ fileName = QLatin1Char(':') + url.path();
+ else
+ fileName = url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
+ }
+
+ QFileInfo fi(fileName);
+ if (fi.isDir()) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString());
+ setError(QNetworkReply::ContentOperationNotPermittedError, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError));
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ return;
+ }
+
+ d->realFile.setFileName(fileName);
+ bool opened = d->realFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+
+ // could we open the file?
+ if (!opened) {
+ QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2")
+ .arg(d->realFile.fileName(), d->realFile.errorString());
+
+ if (d->realFile.exists()) {
+ setError(QNetworkReply::ContentAccessDenied, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied));
+ } else {
+ setError(QNetworkReply::ContentNotFoundError, msg);
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
+ }
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+ return;
+ }
+
+ setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
+ d->realFileSize = fi.size();
+ setHeader(QNetworkRequest::ContentLengthHeader, d->realFileSize);
+
+ QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
+ Q_ARG(qint64, d->realFileSize), Q_ARG(qint64, d->realFileSize));
+ QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+}
+void QNetworkReplyFileImpl::close()
+{
+ Q_D(QNetworkReplyFileImpl);
+ QNetworkReply::close();
+ d->realFile.close();
+}
+
+void QNetworkReplyFileImpl::abort()
+{
+ Q_D(QNetworkReplyFileImpl);
+ QNetworkReply::close();
+ d->realFile.close();
+}
+
+qint64 QNetworkReplyFileImpl::bytesAvailable() const
+{
+ Q_D(const QNetworkReplyFileImpl);
+ return QNetworkReply::bytesAvailable() + d->realFile.bytesAvailable();
+}
+
+bool QNetworkReplyFileImpl::isSequential () const
+{
+ return true;
+}
+
+qint64 QNetworkReplyFileImpl::size() const
+{
+ Q_D(const QNetworkReplyFileImpl);
+ return d->realFileSize;
+}
+
+/*!
+ \internal
+*/
+qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen)
+{
+ Q_D(QNetworkReplyFileImpl);
+ qint64 ret = d->realFile.read(data, maxlen);
+ if (ret == 0 && bytesAvailable() == 0)
+ return -1;
+ else
+ return ret;
+}
+
+
+QT_END_NAMESPACE
+
+#include "moc_qnetworkreplyfileimpl_p.cpp"
+
diff --git a/src/network/access/qnetworkreplyfileimpl_p.h b/src/network/access/qnetworkreplyfileimpl_p.h
new file mode 100644
index 0000000000..c5126de80a
--- /dev/null
+++ b/src/network/access/qnetworkreplyfileimpl_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QNETWORKREPLYFILEIMPL_H
+#define QNETWORKREPLYFILEIMPL_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 <QFile>
+#include <QAbstractFileEngine>
+
+QT_BEGIN_NAMESPACE
+
+
+class QNetworkReplyFileImplPrivate;
+class QNetworkReplyFileImpl: public QNetworkReply
+{
+ Q_OBJECT
+public:
+ QNetworkReplyFileImpl(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op);
+ ~QNetworkReplyFileImpl();
+ virtual void abort();
+
+ // reimplemented from QNetworkReply
+ virtual void close();
+ virtual qint64 bytesAvailable() const;
+ virtual bool isSequential () const;
+ qint64 size() const;
+
+ virtual qint64 readData(char *data, qint64 maxlen);
+
+ Q_DECLARE_PRIVATE(QNetworkReplyFileImpl)
+};
+
+class QNetworkReplyFileImplPrivate: public QNetworkReplyPrivate
+{
+public:
+ QNetworkReplyFileImplPrivate();
+
+ QFile realFile;
+ qint64 realFileSize;
+
+ Q_DECLARE_PUBLIC(QNetworkReplyFileImpl)
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKREPLYFILEIMPL_H
diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp
new file mode 100644
index 0000000000..9eb505d1b7
--- /dev/null
+++ b/src/network/access/qnetworkreplyimpl.cpp
@@ -0,0 +1,1087 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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 "QtNetwork/qnetworksession.h"
+#include "qnetworkaccesshttpbackend_p.h"
+#include "qnetworkaccessmanager_p.h"
+
+#include <QtCore/QCoreApplication>
+
+Q_DECLARE_METATYPE(QSharedPointer<char>)
+
+QT_BEGIN_NAMESPACE
+
+inline QNetworkReplyImplPrivate::QNetworkReplyImplPrivate()
+ : backend(0), outgoingData(0),
+ copyDevice(0),
+ cacheEnabled(false), cacheSaveDevice(0),
+ notificationHandlingPaused(false),
+ bytesDownloaded(0), lastBytesDownloaded(-1), bytesUploaded(-1), preMigrationDownloaded(-1),
+ httpStatusCode(0),
+ state(Idle)
+ , downloadBufferReadPosition(0)
+ , downloadBufferCurrentSize(0)
+ , downloadBufferMaximumSize(0)
+ , downloadBuffer(0)
+{
+}
+
+void QNetworkReplyImplPrivate::_q_startOperation()
+{
+ // ensure this function is only being called once
+ if (state == Working || state == Finished) {
+ qDebug("QNetworkReplyImpl::_q_startOperation was called more than once");
+ return;
+ }
+ state = Working;
+
+ // note: if that method is called directly, it cannot happen that the backend is 0,
+ // because we just checked via a qobject_cast that we got a http backend (see
+ // QNetworkReplyImplPrivate::setup())
+ if (!backend) {
+ error(QNetworkReplyImpl::ProtocolUnknownError,
+ QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown").arg(url.scheme())); // not really true!;
+ finished();
+ return;
+ }
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ if (!backend->start()) { // ### we should call that method even if bearer is not used
+ // backend failed to start because the session state is not Connected.
+ // QNetworkAccessManager will call reply->backend->start() again for us when the session
+ // state changes.
+ state = WaitingForSession;
+
+ QNetworkSession *session = manager->d_func()->networkSession.data();
+
+ if (session) {
+ Q_Q(QNetworkReplyImpl);
+
+ QObject::connect(session, SIGNAL(error(QNetworkSession::SessionError)),
+ q, SLOT(_q_networkSessionFailed()));
+
+ if (!session->isOpen())
+ session->open();
+ } else {
+ qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
+ }
+
+ return;
+ }
+#endif
+
+ if (backend && backend->isSynchronous()) {
+ state = Finished;
+ q_func()->setFinished(true);
+ } else {
+ if (state != Finished) {
+ if (operation == QNetworkAccessManager::GetOperation)
+ pendingNotifications.append(NotifyDownstreamReadyWrite);
+
+ handleNotifications();
+ }
+ }
+}
+
+void QNetworkReplyImplPrivate::_q_copyReadyRead()
+{
+ Q_Q(QNetworkReplyImpl);
+ if (state != Working)
+ return;
+ if (!copyDevice || !q->isOpen())
+ return;
+
+ // FIXME Optimize to use download buffer if it is a QBuffer.
+ // Needs to be done where sendCacheContents() (?) of HTTP is emitting
+ // metaDataChanged ?
+
+ forever {
+ qint64 bytesToRead = nextDownstreamBlockSize();
+ if (bytesToRead == 0)
+ // we'll be called again, eventually
+ break;
+
+ bytesToRead = qBound<qint64>(1, bytesToRead, copyDevice->bytesAvailable());
+ QByteArray byteData;
+ byteData.resize(bytesToRead);
+ qint64 bytesActuallyRead = copyDevice->read(byteData.data(), byteData.size());
+ if (bytesActuallyRead == -1) {
+ byteData.clear();
+ backendNotify(NotifyCopyFinished);
+ break;
+ }
+
+ byteData.resize(bytesActuallyRead);
+ readBuffer.append(byteData);
+
+ if (!copyDevice->isSequential() && copyDevice->atEnd()) {
+ backendNotify(NotifyCopyFinished);
+ bytesDownloaded += bytesActuallyRead;
+ break;
+ }
+
+ bytesDownloaded += bytesActuallyRead;
+ }
+
+ if (bytesDownloaded == lastBytesDownloaded) {
+ // we didn't read anything
+ return;
+ }
+
+ lastBytesDownloaded = bytesDownloaded;
+ QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ if (preMigrationDownloaded != Q_INT64_C(-1))
+ totalSize = totalSize.toLongLong() + preMigrationDownloaded;
+ pauseNotificationHandling();
+ // emit readyRead before downloadProgress incase this will cause events to be
+ // processed and we get into a recursive call (as in QProgressDialog).
+ emit q->readyRead();
+ emit q->downloadProgress(bytesDownloaded,
+ totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+ resumeNotificationHandling();
+}
+
+void QNetworkReplyImplPrivate::_q_copyReadChannelFinished()
+{
+ _q_copyReadyRead();
+}
+
+void QNetworkReplyImplPrivate::_q_bufferOutgoingDataFinished()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ // make sure this is only called once, ever.
+ //_q_bufferOutgoingData may call it or the readChannelFinished emission
+ if (state != Buffering)
+ return;
+
+ // disconnect signals
+ QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
+ QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
+
+ // finally, start the request
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+}
+
+void QNetworkReplyImplPrivate::_q_bufferOutgoingData()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ if (!outgoingDataBuffer) {
+ // first call, create our buffer
+ outgoingDataBuffer = QSharedPointer<QRingBuffer>(new QRingBuffer());
+
+ QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData()));
+ QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished()));
+ }
+
+ qint64 bytesBuffered = 0;
+ qint64 bytesToBuffer = 0;
+
+ // read data into our buffer
+ forever {
+ bytesToBuffer = outgoingData->bytesAvailable();
+ // unknown? just try 2 kB, this also ensures we always try to read the EOF
+ if (bytesToBuffer <= 0)
+ bytesToBuffer = 2*1024;
+
+ char *dst = outgoingDataBuffer->reserve(bytesToBuffer);
+ bytesBuffered = outgoingData->read(dst, bytesToBuffer);
+
+ if (bytesBuffered == -1) {
+ // EOF has been reached.
+ outgoingDataBuffer->chop(bytesToBuffer);
+
+ _q_bufferOutgoingDataFinished();
+ break;
+ } else if (bytesBuffered == 0) {
+ // nothing read right now, just wait until we get called again
+ outgoingDataBuffer->chop(bytesToBuffer);
+
+ break;
+ } else {
+ // don't break, try to read() again
+ outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered);
+ }
+ }
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+void QNetworkReplyImplPrivate::_q_networkSessionConnected()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ if (manager.isNull())
+ return;
+
+ QNetworkSession *session = manager->d_func()->networkSession.data();
+ if (!session)
+ return;
+
+ if (session->state() != QNetworkSession::Connected)
+ return;
+
+ switch (state) {
+ case QNetworkReplyImplPrivate::Buffering:
+ case QNetworkReplyImplPrivate::Working:
+ case QNetworkReplyImplPrivate::Reconnecting:
+ // Migrate existing downloads to new network connection.
+ migrateBackend();
+ break;
+ case QNetworkReplyImplPrivate::WaitingForSession:
+ // Start waiting requests.
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+ break;
+ default:
+ ;
+ }
+}
+
+void QNetworkReplyImplPrivate::_q_networkSessionFailed()
+{
+ // Abort waiting and working replies.
+ if (state == WaitingForSession || state == Working) {
+ state = Working;
+ error(QNetworkReplyImpl::UnknownNetworkError,
+ QCoreApplication::translate("QNetworkReply", "Network session error."));
+ finished();
+ }
+}
+#endif
+
+void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req,
+ QIODevice *data)
+{
+ Q_Q(QNetworkReplyImpl);
+
+ outgoingData = data;
+ request = req;
+ url = request.url();
+ operation = op;
+
+ q->QIODevice::open(QIODevice::ReadOnly);
+ // Internal code that does a HTTP reply for the synchronous Ajax
+ // in QtWebKit.
+ QVariant synchronousHttpAttribute = req.attribute(
+ static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
+ // The synchronous HTTP is a corner case, we will put all upload data in one big QByteArray in the outgoingDataBuffer.
+ // Yes, this is not the most efficient thing to do, but on the other hand synchronous XHR needs to die anyway.
+ if (synchronousHttpAttribute.toBool() && outgoingData) {
+ outgoingDataBuffer = QSharedPointer<QRingBuffer>(new QRingBuffer());
+ qint64 previousDataSize = 0;
+ do {
+ previousDataSize = outgoingDataBuffer->size();
+ outgoingDataBuffer->append(outgoingData->readAll());
+ } while (outgoingDataBuffer->size() != previousDataSize);
+ }
+
+ if (backend)
+ backend->setSynchronous(synchronousHttpAttribute.toBool());
+
+
+ if (outgoingData && backend && !backend->isSynchronous()) {
+ // there is data to be uploaded, e.g. HTTP POST.
+
+ if (!backend->needsResetableUploadData() || !outgoingData->isSequential()) {
+ // backend does not need upload buffering or
+ // fixed size non-sequential
+ // just start the operation
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+ } else {
+ bool bufferingDisallowed =
+ req.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
+ false).toBool();
+
+ if (bufferingDisallowed) {
+ // if a valid content-length header for the request was supplied, we can disable buffering
+ // if not, we will buffer anyway
+ if (req.header(QNetworkRequest::ContentLengthHeader).isValid()) {
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+ } else {
+ state = Buffering;
+ QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
+ }
+ } else {
+ // _q_startOperation will be called when the buffering has finished.
+ state = Buffering;
+ QMetaObject::invokeMethod(q, "_q_bufferOutgoingData", Qt::QueuedConnection);
+ }
+ }
+ } else {
+ // for HTTP, we want to send out the request as fast as possible to the network, without
+ // invoking methods in a QueuedConnection
+#ifndef QT_NO_HTTP
+ if (qobject_cast<QNetworkAccessHttpBackend *>(backend) || (backend && backend->isSynchronous())) {
+ _q_startOperation();
+ } else {
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+ }
+#else
+ if (backend && backend->isSynchronous())
+ _q_startOperation();
+ else
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+#endif // QT_NO_HTTP
+ }
+}
+
+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()
+{
+ if (notificationHandlingPaused)
+ return;
+
+ NotificationQueue current = pendingNotifications;
+ pendingNotifications.clear();
+
+ if (state != Working)
+ return;
+
+ while (state == Working && !current.isEmpty()) {
+ InternalNotifications notification = current.dequeue();
+ switch (notification) {
+ case NotifyDownstreamReadyWrite:
+ if (copyDevice)
+ _q_copyReadyRead();
+ else
+ backend->downstreamReadyWrite();
+ break;
+
+ case NotifyCloseDownstreamChannel:
+ backend->closeDownstreamChannel();
+ break;
+
+ case NotifyCopyFinished: {
+ QIODevice *dev = copyDevice;
+ copyDevice = 0;
+ backend->copyFinished(dev);
+ break;
+ }
+ }
+ }
+}
+
+// Do not handle the notifications while we are emitting downloadProgress
+// or readyRead
+void QNetworkReplyImplPrivate::pauseNotificationHandling()
+{
+ notificationHandlingPaused = true;
+}
+
+// Resume notification handling
+void QNetworkReplyImplPrivate::resumeNotificationHandling()
+{
+ Q_Q(QNetworkReplyImpl);
+ notificationHandlingPaused = false;
+ if (pendingNotifications.size() >= 1)
+ QCoreApplication::postEvent(q, new QEvent(QEvent::NetworkReplyUpdated));
+}
+
+QAbstractNetworkCache *QNetworkReplyImplPrivate::networkCache() const
+{
+ if (!backend)
+ return 0;
+ return backend->networkCache();
+}
+
+void QNetworkReplyImplPrivate::createCache()
+{
+ // check if we can save and if we're allowed to
+ if (!networkCache()
+ || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool()
+ || request.attribute(QNetworkRequest::CacheLoadControlAttribute,
+ QNetworkRequest::PreferNetwork).toInt()
+ == QNetworkRequest::AlwaysNetwork)
+ 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::emitUploadProgress(qint64 bytesSent, qint64 bytesTotal)
+{
+ Q_Q(QNetworkReplyImpl);
+ bytesUploaded = bytesSent;
+ pauseNotificationHandling();
+ emit q->uploadProgress(bytesSent, bytesTotal);
+ resumeNotificationHandling();
+}
+
+
+qint64 QNetworkReplyImplPrivate::nextDownstreamBlockSize() const
+{
+ enum { DesiredBufferSize = 32 * 1024 };
+ if (readBufferMaxSize == 0)
+ return DesiredBufferSize;
+
+ return qMax<qint64>(0, readBufferMaxSize - readBuffer.byteAmount());
+}
+
+void QNetworkReplyImplPrivate::initCacheSaveDevice()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ // The disk cache does not support partial content, so don't even try to
+ // save any such content into the cache.
+ if (q->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 206) {
+ cacheEnabled = false;
+ return;
+ }
+
+ // save the meta data
+ QNetworkCacheMetaData metaData;
+ metaData.setUrl(url);
+ metaData = backend->fetchCacheMetaData(metaData);
+
+ // save the redirect request also in the cache
+ QVariant redirectionTarget = q->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirectionTarget.isValid()) {
+ QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
+ attributes.insert(QNetworkRequest::RedirectionTargetAttribute, redirectionTarget);
+ metaData.setAttributes(attributes);
+ }
+
+ 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;
+ }
+}
+
+// we received downstream data and send this to the cache
+// and to our readBuffer (which in turn gets read by the user of QNetworkReply)
+void QNetworkReplyImplPrivate::appendDownstreamData(QByteDataBuffer &data)
+{
+ Q_Q(QNetworkReplyImpl);
+ if (!q->isOpen())
+ return;
+
+ if (cacheEnabled && !cacheSaveDevice) {
+ initCacheSaveDevice();
+ }
+
+ qint64 bytesWritten = 0;
+ for (int i = 0; i < data.bufferCount(); i++) {
+ QByteArray const &item = data[i];
+
+ if (cacheSaveDevice)
+ cacheSaveDevice->write(item.constData(), item.size());
+ readBuffer.append(item);
+
+ bytesWritten += item.size();
+ }
+ data.clear();
+
+ bytesDownloaded += bytesWritten;
+ lastBytesDownloaded = bytesDownloaded;
+
+ appendDownstreamDataSignalEmissions();
+}
+
+void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ if (preMigrationDownloaded != Q_INT64_C(-1))
+ totalSize = totalSize.toLongLong() + preMigrationDownloaded;
+ pauseNotificationHandling();
+ // important: At the point of this readyRead(), the data parameter list must be empty,
+ // else implicit sharing will trigger memcpy when the user is reading data!
+ emit q->readyRead();
+ // emit readyRead before downloadProgress incase this will cause events to be
+ // processed and we get into a recursive call (as in QProgressDialog).
+ emit q->downloadProgress(bytesDownloaded,
+ totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+
+ resumeNotificationHandling();
+ // do we still have room in the buffer?
+ if (nextDownstreamBlockSize() > 0)
+ backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
+}
+
+// this is used when it was fetched from the cache, right?
+void QNetworkReplyImplPrivate::appendDownstreamData(QIODevice *data)
+{
+ Q_Q(QNetworkReplyImpl);
+ if (!q->isOpen())
+ return;
+
+ // 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::appendDownstreamData(const QByteArray &data)
+{
+ Q_UNUSED(data)
+ // TODO implement
+
+ // TODO call
+
+ qFatal("QNetworkReplyImplPrivate::appendDownstreamData not implemented");
+}
+
+static void downloadBufferDeleter(char *ptr)
+{
+ delete[] ptr;
+}
+
+char* QNetworkReplyImplPrivate::getDownloadBuffer(qint64 size)
+{
+ Q_Q(QNetworkReplyImpl);
+
+ if (!downloadBuffer) {
+ // We are requested to create it
+ // Check attribute() if allocating a buffer of that size can be allowed
+ QVariant bufferAllocationPolicy = request.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
+ if (bufferAllocationPolicy.isValid() && bufferAllocationPolicy.toLongLong() >= size) {
+ downloadBufferCurrentSize = 0;
+ downloadBufferMaximumSize = size;
+ downloadBuffer = new char[downloadBufferMaximumSize]; // throws if allocation fails
+ downloadBufferPointer = QSharedPointer<char>(downloadBuffer, downloadBufferDeleter);
+
+ q->setAttribute(QNetworkRequest::DownloadBufferAttribute, qVariantFromValue<QSharedPointer<char> > (downloadBufferPointer));
+ }
+ }
+
+ return downloadBuffer;
+}
+
+void QNetworkReplyImplPrivate::setDownloadBuffer(QSharedPointer<char> sp, qint64 size)
+{
+ Q_Q(QNetworkReplyImpl);
+
+ downloadBufferPointer = sp;
+ downloadBuffer = downloadBufferPointer.data();
+ downloadBufferCurrentSize = 0;
+ downloadBufferMaximumSize = size;
+ q->setAttribute(QNetworkRequest::DownloadBufferAttribute, qVariantFromValue<QSharedPointer<char> > (downloadBufferPointer));
+}
+
+
+void QNetworkReplyImplPrivate::appendDownstreamDataDownloadBuffer(qint64 bytesReceived, qint64 bytesTotal)
+{
+ Q_Q(QNetworkReplyImpl);
+ if (!q->isOpen())
+ return;
+
+ if (cacheEnabled && !cacheSaveDevice)
+ initCacheSaveDevice();
+
+ if (cacheSaveDevice && bytesReceived == bytesTotal) {
+// if (lastBytesDownloaded == -1)
+// lastBytesDownloaded = 0;
+// cacheSaveDevice->write(downloadBuffer + lastBytesDownloaded, bytesReceived - lastBytesDownloaded);
+
+ // Write everything in one go if we use a download buffer. might be more performant.
+ cacheSaveDevice->write(downloadBuffer, bytesTotal);
+ }
+
+ bytesDownloaded = bytesReceived;
+ lastBytesDownloaded = bytesReceived;
+
+ downloadBufferCurrentSize = bytesReceived;
+
+ // Only emit readyRead when actual data is there
+ // emit readyRead before downloadProgress incase this will cause events to be
+ // processed and we get into a recursive call (as in QProgressDialog).
+ if (bytesDownloaded > 0)
+ emit q->readyRead();
+ emit q->downloadProgress(bytesDownloaded, bytesTotal);
+}
+
+void QNetworkReplyImplPrivate::finished()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ if (state == Finished || state == Aborted || state == WaitingForSession)
+ return;
+
+ pauseNotificationHandling();
+ QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ if (preMigrationDownloaded != Q_INT64_C(-1))
+ totalSize = totalSize.toLongLong() + preMigrationDownloaded;
+
+ if (!manager.isNull()) {
+#ifndef QT_NO_BEARERMANAGEMENT
+ QNetworkSession *session = manager->d_func()->networkSession.data();
+ if (session && session->state() == QNetworkSession::Roaming &&
+ state == Working && errorCode != QNetworkReply::OperationCanceledError) {
+ // only content with a known size will fail with a temporary network failure error
+ if (!totalSize.isNull()) {
+ if (bytesDownloaded != totalSize) {
+ if (migrateBackend()) {
+ // either we are migrating or the request is finished/aborted
+ if (state == Reconnecting || state == WaitingForSession) {
+ resumeNotificationHandling();
+ return; // exit early if we are migrating.
+ }
+ } else {
+ error(QNetworkReply::TemporaryNetworkFailureError,
+ QNetworkReply::tr("Temporary network failure."));
+ }
+ }
+ }
+ }
+#endif
+ }
+ resumeNotificationHandling();
+
+ state = Finished;
+ q->setFinished(true);
+
+ pendingNotifications.clear();
+
+ pauseNotificationHandling();
+ if (totalSize.isNull() || totalSize == -1) {
+ emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
+ }
+
+ if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
+ emit q->uploadProgress(0, 0);
+ resumeNotificationHandling();
+
+ // if we don't know the total size of or we received everything save the cache
+ if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
+ 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
+ pauseNotificationHandling();
+ emit q->readChannelFinished();
+ emit q->finished();
+ resumeNotificationHandling();
+}
+
+void QNetworkReplyImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
+{
+ Q_Q(QNetworkReplyImpl);
+ // Can't set and emit multiple errors.
+ if (errorCode != QNetworkReply::NoError) {
+ qWarning() << "QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.";
+ return;
+ }
+
+ 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);
+ // 1. do we have cookies?
+ // 2. are we allowed to set them?
+ if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && !manager.isNull()
+ && (static_cast<QNetworkRequest::LoadControl>
+ (request.attribute(QNetworkRequest::CookieSaveControlAttribute,
+ QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Automatic)) {
+ 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);
+
+ // This code removes the data from the cache if it was prematurely aborted.
+ // See QNetworkReplyImplPrivate::completeCacheSave(), we disable caching there after the cache
+ // save had been properly finished. So if it is still enabled it means we got deleted/aborted.
+ if (d->isCachingEnabled())
+ d->networkCache()->remove(url());
+}
+
+void QNetworkReplyImpl::abort()
+{
+ Q_D(QNetworkReplyImpl);
+ if (d->state == QNetworkReplyImplPrivate::Finished || d->state == QNetworkReplyImplPrivate::Aborted)
+ return;
+
+ // stop both upload and download
+ 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) {
+ // call finished which will emit signals
+ d->error(OperationCanceledError, tr("Operation canceled"));
+ d->finished();
+ }
+ d->state = QNetworkReplyImplPrivate::Aborted;
+
+ // finished may access the backend
+ if (d->backend) {
+ d->backend->deleteLater();
+ d->backend = 0;
+ }
+}
+
+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();
+
+ // call finished which will emit signals
+ d->error(OperationCanceledError, tr("Operation canceled"));
+ d->finished();
+}
+
+bool QNetworkReplyImpl::canReadLine () const
+{
+ Q_D(const QNetworkReplyImpl);
+ return QNetworkReply::canReadLine() || d->readBuffer.canReadLine();
+}
+
+
+/*!
+ 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
+{
+ // Special case for the "zero copy" download buffer
+ Q_D(const QNetworkReplyImpl);
+ if (d->downloadBuffer) {
+ qint64 maxAvail = d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
+ return QNetworkReply::bytesAvailable() + maxAvail;
+ }
+
+ return QNetworkReply::bytesAvailable() + d_func()->readBuffer.byteAmount();
+}
+
+void QNetworkReplyImpl::setReadBufferSize(qint64 size)
+{
+ Q_D(QNetworkReplyImpl);
+ if (size > d->readBufferMaxSize &&
+ size > d->readBuffer.byteAmount())
+ d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
+
+ QNetworkReply::setReadBufferSize(size);
+
+ if (d->backend)
+ d->backend->setDownstreamLimited(d->readBufferMaxSize > 0);
+}
+
+#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();
+}
+
+void QNetworkReplyImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
+{
+ Q_D(QNetworkReplyImpl);
+ if (d->backend)
+ d->backend->ignoreSslErrors(errors);
+}
+#endif // QT_NO_OPENSSL
+
+/*!
+ \internal
+*/
+qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen)
+{
+ Q_D(QNetworkReplyImpl);
+
+ // Special case code if we have the "zero copy" download buffer
+ if (d->downloadBuffer) {
+ qint64 maxAvail = qMin<qint64>(d->downloadBufferCurrentSize - d->downloadBufferReadPosition, maxlen);
+ if (maxAvail == 0)
+ return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0;
+ // FIXME what about "Aborted" state?
+ qMemCopy(data, d->downloadBuffer + d->downloadBufferReadPosition, maxAvail);
+ d->downloadBufferReadPosition += maxAvail;
+ return maxAvail;
+ }
+
+
+ if (d->readBuffer.isEmpty())
+ return d->state == QNetworkReplyImplPrivate::Finished ? -1 : 0;
+ // FIXME what about "Aborted" state?
+
+ d->backendNotify(QNetworkReplyImplPrivate::NotifyDownstreamReadyWrite);
+ if (maxlen == 1) {
+ // optimization for getChar()
+ *data = d->readBuffer.getChar();
+ return 1;
+ }
+
+ maxlen = qMin<qint64>(maxlen, d->readBuffer.byteAmount());
+ 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);
+}
+
+/*
+ Migrates the backend of the QNetworkReply to a new network connection if required. Returns
+ true if the reply is migrated or it is not required; otherwise returns false.
+*/
+bool QNetworkReplyImplPrivate::migrateBackend()
+{
+ Q_Q(QNetworkReplyImpl);
+
+ // Network reply is already finished or aborted, don't need to migrate.
+ if (state == Finished || state == Aborted)
+ return true;
+
+ // Backend does not support resuming download.
+ if (!backend->canResume())
+ return false;
+
+ // Request has outgoing data, not migrating.
+ if (outgoingData)
+ return false;
+
+ // Request is serviced from the cache, don't need to migrate.
+ if (copyDevice)
+ return true;
+
+ state = QNetworkReplyImplPrivate::Reconnecting;
+
+ if (backend) {
+ delete backend;
+ backend = 0;
+ }
+
+ cookedHeaders.clear();
+ rawHeaders.clear();
+
+ preMigrationDownloaded = bytesDownloaded;
+
+ backend = manager->d_func()->findBackend(operation, request);
+
+ if (backend) {
+ backend->setParent(q);
+ backend->reply = this;
+ backend->setResumeOffset(bytesDownloaded);
+ }
+
+#ifndef QT_NO_HTTP
+ if (qobject_cast<QNetworkAccessHttpBackend *>(backend)) {
+ _q_startOperation();
+ } else {
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+ }
+#else
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+#endif // QT_NO_HTTP
+
+ return true;
+}
+
+#ifndef QT_NO_BEARERMANAGEMENT
+QDisabledNetworkReply::QDisabledNetworkReply(QObject *parent,
+ const QNetworkRequest &req,
+ QNetworkAccessManager::Operation op)
+: QNetworkReply(parent)
+{
+ setRequest(req);
+ setUrl(req.url());
+ setOperation(op);
+
+ qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
+
+ QString msg = QCoreApplication::translate("QNetworkAccessManager",
+ "Network access is disabled.");
+ setError(UnknownNetworkError, msg);
+
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, UnknownNetworkError));
+ QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
+}
+
+QDisabledNetworkReply::~QDisabledNetworkReply()
+{
+}
+#endif
+
+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 0000000000..1a9ab7eda7
--- /dev/null
+++ b/src/network/access/qnetworkreplyimpl_p.h
@@ -0,0 +1,238 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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 "QtCore/qbuffer.h"
+#include "private/qringbuffer_p.h"
+#include "private/qbytedata_p.h"
+#include <QSharedPointer>
+
+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 / QIODevice
+ virtual void close();
+ virtual qint64 bytesAvailable() const;
+ virtual void setReadBufferSize(qint64 size);
+ virtual bool canReadLine () const;
+
+ 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();
+ Q_INVOKABLE virtual void ignoreSslErrorsImplementation(const QList<QSslError> &errors);
+#endif
+
+ Q_DECLARE_PRIVATE(QNetworkReplyImpl)
+ Q_PRIVATE_SLOT(d_func(), void _q_startOperation())
+ Q_PRIVATE_SLOT(d_func(), void _q_copyReadyRead())
+ Q_PRIVATE_SLOT(d_func(), void _q_copyReadChannelFinished())
+ Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingData())
+ Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingDataFinished())
+#ifndef QT_NO_BEARERMANAGEMENT
+ Q_PRIVATE_SLOT(d_func(), void _q_networkSessionConnected())
+ Q_PRIVATE_SLOT(d_func(), void _q_networkSessionFailed())
+#endif
+};
+
+class QNetworkReplyImplPrivate: public QNetworkReplyPrivate
+{
+public:
+ enum InternalNotifications {
+ NotifyDownstreamReadyWrite,
+ NotifyCloseDownstreamChannel,
+ NotifyCopyFinished
+ };
+
+ enum State {
+ Idle, // The reply is idle.
+ Buffering, // The reply is buffering outgoing data.
+ Working, // The reply is uploading/downloading data.
+ Finished, // The reply has finished.
+ Aborted, // The reply has been aborted.
+ WaitingForSession, // The reply is waiting for the session to open before connecting.
+ Reconnecting // The reply will reconnect to once roaming has completed.
+ };
+
+ typedef QQueue<InternalNotifications> NotificationQueue;
+
+ QNetworkReplyImplPrivate();
+
+ void _q_startOperation();
+ void _q_sourceReadyRead();
+ void _q_sourceReadChannelFinished();
+ void _q_copyReadyRead();
+ void _q_copyReadChannelFinished();
+ void _q_bufferOutgoingData();
+ void _q_bufferOutgoingDataFinished();
+#ifndef QT_NO_BEARERMANAGEMENT
+ void _q_networkSessionConnected();
+ void _q_networkSessionFailed();
+#endif
+
+ void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
+ QIODevice *outgoingData);
+
+ void pauseNotificationHandling();
+ void resumeNotificationHandling();
+ 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);
+ void emitUploadProgress(qint64 bytesSent, qint64 bytesTotal);
+ qint64 nextDownstreamBlockSize() const;
+
+ void initCacheSaveDevice();
+ void appendDownstreamDataSignalEmissions();
+ void appendDownstreamData(QByteDataBuffer &data);
+ void appendDownstreamData(QIODevice *data);
+ void appendDownstreamData(const QByteArray &data);
+
+ void setDownloadBuffer(QSharedPointer<char> sp, qint64 size);
+ char* getDownloadBuffer(qint64 size);
+ void appendDownstreamDataDownloadBuffer(qint64, qint64);
+
+ 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;
+ QSharedPointer<QRingBuffer> outgoingDataBuffer;
+ QIODevice *copyDevice;
+ QAbstractNetworkCache *networkCache() const;
+
+ bool migrateBackend();
+
+ bool cacheEnabled;
+ QIODevice *cacheSaveDevice;
+
+ NotificationQueue pendingNotifications;
+ bool notificationHandlingPaused;
+
+ QUrl urlForLastAuthentication;
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy lastProxyAuthentication;
+ QList<QNetworkProxy> proxyList;
+#endif
+
+ // Used for normal downloading. For "zero copy" the downloadBuffer is used
+ QByteDataBuffer readBuffer;
+ qint64 bytesDownloaded;
+ qint64 lastBytesDownloaded;
+ qint64 bytesUploaded;
+ qint64 preMigrationDownloaded;
+
+ QString httpReasonPhrase;
+ int httpStatusCode;
+
+ State state;
+
+ // only used when the "zero copy" style is used. Else readBuffer is used.
+ // Please note that the whole "zero copy" download buffer API is private right now. Do not use it.
+ qint64 downloadBufferReadPosition;
+ qint64 downloadBufferCurrentSize;
+ qint64 downloadBufferMaximumSize;
+ QSharedPointer<char> downloadBufferPointer;
+ char* downloadBuffer;
+
+ Q_DECLARE_PUBLIC(QNetworkReplyImpl)
+};
+
+#ifndef QT_NO_BEARERMANAGEMENT
+class QDisabledNetworkReply : public QNetworkReply
+{
+ Q_OBJECT
+
+public:
+ QDisabledNetworkReply(QObject *parent, const QNetworkRequest &req,
+ QNetworkAccessManager::Operation op);
+ ~QDisabledNetworkReply();
+
+ void abort() { }
+protected:
+ qint64 readData(char *, qint64) { return -1; }
+};
+#endif
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
new file mode 100644
index 0000000000..338969a909
--- /dev/null
+++ b/src/network/access/qnetworkrequest.cpp
@@ -0,0 +1,1032 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "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"
+
+#include <ctype.h>
+#ifndef QT_NO_DATESTRING
+# include <stdio.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QNetworkRequest
+ \brief The QNetworkRequest class holds a request to be sent with QNetworkAccessManager.
+ \since 4.4
+
+ \ingroup network
+ \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
+ \since 4.7
+
+ 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.
+ The returned URL might be relative. Use QUrl::resolved()
+ to create an absolute URL out of it.
+
+ \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).
+
+ \value SourceIsFromCacheAttribute
+ Replies only, type: QVariant::Bool (default: false)
+ Indicates whether the data was obtained from cache
+ or not.
+
+ \value DoNotBufferUploadDataAttribute
+ Requests only, type: QVariant::Bool (default: false)
+ Indicates whether the QNetworkAccessManager code is
+ allowed to buffer the upload data, e.g. when doing a HTTP POST.
+ When using this flag with sequential upload data, the ContentLengthHeader
+ header must be set.
+
+ \value HttpPipeliningAllowedAttribute
+ Requests only, type: QVariant::Bool (default: false)
+ Indicates whether the QNetworkAccessManager code is
+ allowed to use HTTP pipelining with this request.
+
+ \value HttpPipeliningWasUsedAttribute
+ Replies only, type: QVariant::Bool
+ Indicates whether the HTTP pipelining was used for receiving
+ this reply.
+
+ \value CustomVerbAttribute
+ Requests only, type: QVariant::ByteArray
+ Holds the value for the custom HTTP verb to send (destined for usage
+ of other verbs than GET, POST, PUT and DELETE). This verb is set
+ when calling QNetworkAccessManager::sendCustomRequest().
+
+ \value CookieLoadControlAttribute
+ Requests only, type: QVariant::Int (default: QNetworkRequest::Automatic)
+ Indicates whether to send 'Cookie' headers in the request.
+ This attribute is set to false by QtWebKit when creating a cross-origin
+ XMLHttpRequest where withCredentials has not been set explicitly to true by the
+ Javascript that created the request.
+ See \l{http://www.w3.org/TR/XMLHttpRequest2/#credentials-flag}{here} for more information.
+ (This value was introduced in 4.7.)
+
+ \value CookieSaveControlAttribute
+ Requests only, type: QVariant::Int (default: QNetworkRequest::Automatic)
+ Indicates whether to save 'Cookie' headers received from the server in reply
+ to the request.
+ This attribute is set to false by QtWebKit when creating a cross-origin
+ XMLHttpRequest where withCredentials has not been set explicitly to true by the
+ Javascript that created the request.
+ See \l{http://www.w3.org/TR/XMLHttpRequest2/#credentials-flag} {here} for more information.
+ (This value was introduced in 4.7.)
+
+ \value AuthenticationReuseAttribute
+ Requests only, type: QVariant::Int (default: QNetworkRequest::Automatic)
+ Indicates whether to use cached authorization credentials in the request,
+ if available. If this is set to QNetworkRequest::Manual and the authentication
+ mechanism is 'Basic' or 'Digest', Qt will not send an an 'Authorization' HTTP
+ header with any cached credentials it may have for the request's URL.
+ This attribute is set to QNetworkRequest::Manual by QtWebKit when creating a cross-origin
+ XMLHttpRequest where withCredentials has not been set explicitly to true by the
+ Javascript that created the request.
+ See \l{http://www.w3.org/TR/XMLHttpRequest2/#credentials-flag} {here} for more information.
+ (This value was introduced in 4.7.)
+
+ \omitvalue MaximumDownloadBufferSizeAttribute
+
+ \omitvalue DownloadBufferAttribute
+
+ \omitvalue SynchronousRequestAttribute
+
+ \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)
+*/
+
+/*!
+ \enum QNetworkRequest::LoadControl
+ \since 4.7
+
+ Indicates if an aspect of the request's loading mechanism has been
+ manually overridden, e.g. by QtWebKit.
+
+ \value Automatic default value: indicates default behaviour.
+
+ \value Manual indicates behaviour has been manually overridden.
+*/
+
+class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
+{
+public:
+ inline QNetworkRequestPrivate()
+ : priority(QNetworkRequest::NormalPriority)
+#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;
+ priority = other.priority;
+
+#ifndef QT_NO_OPENSSL
+ sslConfiguration = 0;
+ if (other.sslConfiguration)
+ sslConfiguration = new QSslConfiguration(*other.sslConfiguration);
+#endif
+ }
+
+ inline bool operator==(const QNetworkRequestPrivate &other) const
+ {
+ return url == other.url &&
+ priority == other.priority &&
+ rawHeaders == other.rawHeaders &&
+ attributes == other.attributes;
+ // don't compare cookedHeaders
+ }
+
+ QUrl url;
+ QNetworkRequest::Priority priority;
+#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(QSslConfiguration::defaultConfiguration());
+ 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), the CA
+ certificates 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
+
+/*!
+ \since 4.6
+
+ Allows setting a reference to the \a object initiating
+ the request.
+
+ For example QtWebKit sets the originating object to the
+ QWebFrame that initiated the request.
+
+ \sa originatingObject()
+*/
+void QNetworkRequest::setOriginatingObject(QObject *object)
+{
+ d->originatingObject = object;
+}
+
+/*!
+ \since 4.6
+
+ Returns a reference to the object that initiated this
+ network request; returns 0 if not set or the object has
+ been destroyed.
+
+ \sa setOriginatingObject()
+*/
+QObject *QNetworkRequest::originatingObject() const
+{
+ return d->originatingObject.data();
+}
+
+/*!
+ \since 4.7
+
+ Return the priority of this request.
+
+ \sa setPriority()
+*/
+QNetworkRequest::Priority QNetworkRequest::priority() const
+{
+ return d->priority;
+}
+
+/*! \enum QNetworkRequest::Priority
+
+ \since 4.7
+
+ This enum lists the possible network request priorities.
+
+ \value HighPriority High priority
+ \value NormalPriority Normal priority
+ \value LowPriority Low priority
+ */
+
+/*!
+ \since 4.7
+
+ Set the priority of this request to \a priority.
+
+ \note The \a priority is only a hint to the network access
+ manager. It can use it or not. Currently it is used for HTTP to
+ decide which request should be sent first to a server.
+
+ \sa priority()
+*/
+void QNetworkRequest::setPriority(Priority priority)
+{
+ d->priority = priority;
+}
+
+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";
+
+ case QNetworkRequest::ContentDispositionHeader:
+ return "Content-Disposition";
+
+ // 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:
+ case QNetworkRequest::ContentDispositionHeader:
+ 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
+
+ switch (tolower(headerName.at(0))) {
+ case 'c':
+ if (qstricmp(headerName.constData(), "content-type") == 0)
+ return QNetworkRequest::ContentTypeHeader;
+ else if (qstricmp(headerName.constData(), "content-length") == 0)
+ return QNetworkRequest::ContentLengthHeader;
+ else if (qstricmp(headerName.constData(), "cookie") == 0)
+ return QNetworkRequest::CookieHeader;
+ break;
+
+ case 'l':
+ if (qstricmp(headerName.constData(), "location") == 0)
+ return QNetworkRequest::LocationHeader;
+ else if (qstricmp(headerName.constData(), "last-modified") == 0)
+ return QNetworkRequest::LastModifiedHeader;
+ break;
+
+ case 's':
+ if (qstricmp(headerName.constData(), "set-cookie") == 0)
+ 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 (const QByteArray &cookie, cookieList) {
+ QList<QNetworkCookie> parsed = QNetworkCookie::parseCookies(cookie.trimmed());
+ if (parsed.count() != 1)
+ return QVariant(); // invalid Cookie: header
+
+ result += parsed;
+ }
+
+ return QVariant::fromValue(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 QVariant::fromValue(QNetworkCookie::parseCookies(value));
+
+ default:
+ Q_ASSERT(0);
+ }
+ return QVariant();
+}
+
+QNetworkHeadersPrivate::RawHeadersList::ConstIterator
+QNetworkHeadersPrivate::findRawHeader(const QByteArray &key) const
+{
+ RawHeadersList::ConstIterator it = rawHeaders.constBegin();
+ RawHeadersList::ConstIterator end = rawHeaders.constEnd();
+ for ( ; it != end; ++it)
+ if (qstricmp(it->first.constData(), key.constData()) == 0)
+ return it;
+
+ return end; // not found
+}
+
+QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::allRawHeaders() const
+{
+ return rawHeaders;
+}
+
+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)
+{
+ RawHeadersList::Iterator it = rawHeaders.begin();
+ while (it != rawHeaders.end()) {
+ if (qstricmp(it->first.constData(), key.constData()) == 0)
+ 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 if (parsedKey == QNetworkRequest::ContentLengthHeader
+ && cookedHeaders.contains(QNetworkRequest::ContentLengthHeader)) {
+ // Only set the cooked header "Content-Length" once.
+ // See bug QTBUG-15311
+ } else {
+ cookedHeaders.insert(parsedKey, parseHeaderValue(parsedKey, value));
+ }
+
+ }
+}
+
+// Fast month string to int conversion. This code
+// assumes that the Month name is correct and that
+// the string is at least three chars long.
+static int name_to_month(const char* month_str)
+{
+ switch (month_str[0]) {
+ case 'J':
+ switch (month_str[1]) {
+ case 'a':
+ return 1;
+ break;
+ case 'u':
+ switch (month_str[2] ) {
+ case 'n':
+ return 6;
+ break;
+ case 'l':
+ return 7;
+ break;
+ }
+ }
+ break;
+ case 'F':
+ return 2;
+ break;
+ case 'M':
+ switch (month_str[2] ) {
+ case 'r':
+ return 3;
+ break;
+ case 'y':
+ return 5;
+ break;
+ }
+ break;
+ case 'A':
+ switch (month_str[1]) {
+ case 'p':
+ return 4;
+ break;
+ case 'u':
+ return 8;
+ break;
+ }
+ break;
+ case 'O':
+ return 10;
+ break;
+ case 'S':
+ return 9;
+ break;
+ case 'N':
+ return 11;
+ break;
+ case 'D':
+ return 12;
+ break;
+ }
+
+ return 0;
+}
+
+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 {
+ // Use sscanf over QLocal/QDateTimeParser for speed reasons. See the
+ // QtWebKit performance benchmarks to get an idea.
+ if (pos == 3) {
+ char month_name[4];
+ int day, year, hour, minute, second;
+ if (sscanf(value.constData(), "%*3s, %d %3s %d %d:%d:%d 'GMT'", &day, month_name, &year, &hour, &minute, &second) == 6)
+ dt = QDateTime(QDate(year, name_to_month(month_name), day), QTime(hour, minute, second));
+ } else {
+ QLocale c = QLocale::c();
+ // eat the weekday, the comma and the space following it
+ QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2);
+ // 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 0000000000..d3bbba770c
--- /dev/null
+++ b/src/network/access/qnetworkrequest.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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,
+ ContentDispositionHeader // added for QMultipartMessage
+ };
+ enum Attribute {
+ HttpStatusCodeAttribute,
+ HttpReasonPhraseAttribute,
+ RedirectionTargetAttribute,
+ ConnectionEncryptedAttribute,
+ CacheLoadControlAttribute,
+ CacheSaveControlAttribute,
+ SourceIsFromCacheAttribute,
+ DoNotBufferUploadDataAttribute,
+ HttpPipeliningAllowedAttribute,
+ HttpPipeliningWasUsedAttribute,
+ CustomVerbAttribute,
+ CookieLoadControlAttribute,
+ AuthenticationReuseAttribute,
+ CookieSaveControlAttribute,
+ MaximumDownloadBufferSizeAttribute, // internal
+ DownloadBufferAttribute, // internal
+ SynchronousRequestAttribute, // internal
+
+ User = 1000,
+ UserMax = 32767
+ };
+ enum CacheLoadControl {
+ AlwaysNetwork,
+ PreferNetwork,
+ PreferCache,
+ AlwaysCache
+ };
+ enum LoadControl {
+ Automatic = 0,
+ Manual
+ };
+
+ enum Priority {
+ HighPriority = 1,
+ NormalPriority = 3,
+ LowPriority = 5
+ };
+
+ 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
+
+ void setOriginatingObject(QObject *object);
+ QObject *originatingObject() const;
+
+ Priority priority() const;
+ void setPriority(Priority priority);
+
+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 0000000000..ea8c56f947
--- /dev/null
+++ b/src/network/access/qnetworkrequest_p.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtNetwork module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef 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"
+#include "QtCore/qsharedpointer.h"
+
+QT_BEGIN_NAMESPACE
+
+// this is the common part between QNetworkRequestPrivate, QNetworkReplyPrivate and QHttpPartPrivate
+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;
+ QWeakPointer<QObject> originatingObject;
+
+ RawHeadersList::ConstIterator findRawHeader(const QByteArray &key) const;
+ RawHeadersList allRawHeaders() const;
+ QList<QByteArray> rawHeadersKeys() const;
+ void setRawHeader(const QByteArray &key, const QByteArray &value);
+ void setAllRawHeaders(const RawHeadersList &list);
+ 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