diff options
Diffstat (limited to 'src/network')
172 files changed, 75632 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, ¤tChunkSize); + 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, © }; + 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, © }; + 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 diff --git a/src/network/bearer/bearer.pri b/src/network/bearer/bearer.pri new file mode 100644 index 0000000000..d58d5ec168 --- /dev/null +++ b/src/network/bearer/bearer.pri @@ -0,0 +1,19 @@ +# Qt network bearer management module + +HEADERS += bearer/qnetworkconfiguration.h \ + bearer/qnetworksession.h \ + bearer/qnetworkconfigmanager.h \ + bearer/qnetworkconfigmanager_p.h \ + bearer/qnetworkconfiguration_p.h \ + bearer/qnetworksession_p.h \ + bearer/qbearerengine_p.h \ + bearer/qbearerplugin_p.h \ + bearer/qsharednetworksession_p.h + +SOURCES += bearer/qnetworksession.cpp \ + bearer/qnetworkconfigmanager.cpp \ + bearer/qnetworkconfiguration.cpp \ + bearer/qnetworkconfigmanager_p.cpp \ + bearer/qbearerengine.cpp \ + bearer/qbearerplugin.cpp \ + bearer/qsharednetworksession.cpp diff --git a/src/network/bearer/qbearerengine.cpp b/src/network/bearer/qbearerengine.cpp new file mode 100644 index 0000000000..7447051018 --- /dev/null +++ b/src/network/bearer/qbearerengine.cpp @@ -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$ +** +****************************************************************************/ + +#include "qbearerengine_p.h" + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +QBearerEngine::QBearerEngine(QObject *parent) + : QObject(parent), mutex(QMutex::Recursive) +{ +} + +QBearerEngine::~QBearerEngine() +{ + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator it; + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator end; + + for (it = snapConfigurations.begin(), end = snapConfigurations.end(); it != end; ++it) { + it.value()->isValid = false; + it.value()->id.clear(); + } + snapConfigurations.clear(); + + for (it = accessPointConfigurations.begin(), end = accessPointConfigurations.end(); + it != end; ++it) { + it.value()->isValid = false; + it.value()->id.clear(); + } + accessPointConfigurations.clear(); + + for (it = userChoiceConfigurations.begin(), end = userChoiceConfigurations.end(); + it != end; ++it) { + it.value()->isValid = false; + it.value()->id.clear(); + } + userChoiceConfigurations.clear(); +} + +bool QBearerEngine::requiresPolling() const +{ + return false; +} + +/* + Returns true if configurations are in use; otherwise returns false. + + If configurations are in use and requiresPolling() returns true, polling will be enabled for + this engine. +*/ +bool QBearerEngine::configurationsInUse() const +{ + QHash<QString, QNetworkConfigurationPrivatePointer>::ConstIterator it; + QHash<QString, QNetworkConfigurationPrivatePointer>::ConstIterator end; + + QMutexLocker locker(&mutex); + + for (it = accessPointConfigurations.constBegin(), + end = accessPointConfigurations.constEnd(); it != end; ++it) { + if (it.value()->ref > 1) + return true; + } + + for (it = snapConfigurations.constBegin(), + end = snapConfigurations.constEnd(); it != end; ++it) { + if (it.value()->ref > 1) + return true; + } + + for (it = userChoiceConfigurations.constBegin(), + end = userChoiceConfigurations.constEnd(); it != end; ++it) { + if (it.value()->ref > 1) + return true; + } + + return false; +} + +#include "moc_qbearerengine_p.cpp" + +#endif // QT_NO_BEARERMANAGEMENT + +QT_END_NAMESPACE diff --git a/src/network/bearer/qbearerengine_p.h b/src/network/bearer/qbearerengine_p.h new file mode 100644 index 0000000000..e246355142 --- /dev/null +++ b/src/network/bearer/qbearerengine_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 QBEARERENGINE_P_H +#define QBEARERENGINE_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 "qnetworkconfiguration_p.h" +#include "qnetworksession.h" +#include "qnetworkconfigmanager.h" + +#include <QtCore/qobject.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> +#include <QtCore/qstring.h> +#include <QtCore/qhash.h> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qmutex.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +class QNetworkConfiguration; + +class Q_NETWORK_EXPORT QBearerEngine : public QObject +{ + Q_OBJECT + + friend class QNetworkConfigurationManagerPrivate; + +public: + explicit QBearerEngine(QObject *parent = 0); + virtual ~QBearerEngine(); + + virtual bool hasIdentifier(const QString &id) = 0; + + virtual QNetworkConfigurationManager::Capabilities capabilities() const = 0; + + virtual QNetworkSessionPrivate *createSessionBackend() = 0; + + virtual QNetworkConfigurationPrivatePointer defaultConfiguration() = 0; + + virtual bool requiresPolling() const; + bool configurationsInUse() const; + +Q_SIGNALS: + void configurationAdded(QNetworkConfigurationPrivatePointer config); + void configurationRemoved(QNetworkConfigurationPrivatePointer config); + void configurationChanged(QNetworkConfigurationPrivatePointer config); + void updateCompleted(); + +protected: + //this table contains an up to date list of all configs at any time. + //it must be updated if configurations change, are added/removed or + //the members of ServiceNetworks change + QHash<QString, QNetworkConfigurationPrivatePointer> accessPointConfigurations; + QHash<QString, QNetworkConfigurationPrivatePointer> snapConfigurations; + QHash<QString, QNetworkConfigurationPrivatePointer> userChoiceConfigurations; + + mutable QMutex mutex; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QBEARERENGINE_P_H diff --git a/src/network/bearer/qbearerplugin.cpp b/src/network/bearer/qbearerplugin.cpp new file mode 100644 index 0000000000..c198d674ac --- /dev/null +++ b/src/network/bearer/qbearerplugin.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 "qbearerplugin_p.h" + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +QBearerEnginePlugin::QBearerEnginePlugin(QObject *parent) + : QObject(parent) +{ +} + +QBearerEnginePlugin::~QBearerEnginePlugin() +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT diff --git a/src/network/bearer/qbearerplugin_p.h b/src/network/bearer/qbearerplugin_p.h new file mode 100644 index 0000000000..a800f90fb5 --- /dev/null +++ b/src/network/bearer/qbearerplugin_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBEARERPLUGIN_P_H +#define QBEARERPLUGIN_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 "qbearerengine_p.h" + +#include <QtCore/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +struct Q_NETWORK_EXPORT QBearerEngineFactoryInterface : public QFactoryInterface +{ + virtual QBearerEngine *create(const QString &key) const = 0; +}; + +#define QBearerEngineFactoryInterface_iid "com.trolltech.Qt.QBearerEngineFactoryInterface" +Q_DECLARE_INTERFACE(QBearerEngineFactoryInterface, QBearerEngineFactoryInterface_iid) + +class Q_NETWORK_EXPORT QBearerEnginePlugin : public QObject, public QBearerEngineFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QBearerEngineFactoryInterface:QFactoryInterface) + +public: + explicit QBearerEnginePlugin(QObject *parent = 0); + virtual ~QBearerEnginePlugin(); + + virtual QStringList keys() const = 0; + virtual QBearerEngine *create(const QString &key) const = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QBEARERPLUGIN_P_H diff --git a/src/network/bearer/qnetworkconfigmanager.cpp b/src/network/bearer/qnetworkconfigmanager.cpp new file mode 100644 index 0000000000..10fe74ce10 --- /dev/null +++ b/src/network/bearer/qnetworkconfigmanager.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** 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 "qnetworkconfigmanager.h" + +#include "qnetworkconfigmanager_p.h" +#include "qbearerengine_p.h" + +#include <QtCore/qstringlist.h> +#include <QtCore/qcoreapplication.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +#define Q_GLOBAL_STATIC_QAPP_DESTRUCTION(TYPE, NAME) \ + static QGlobalStatic<TYPE > this_##NAME \ + = { Q_BASIC_ATOMIC_INITIALIZER(0), false }; \ + static void NAME##_cleanup() \ + { \ + delete this_##NAME.pointer; \ + this_##NAME.pointer = 0; \ + } \ + static TYPE *NAME() \ + { \ + if (!this_##NAME.pointer) { \ + TYPE *x = new TYPE; \ + if (!this_##NAME.pointer.testAndSetOrdered(0, x)) \ + delete x; \ + else { \ + qAddPostRoutine(NAME##_cleanup); \ + this_##NAME.pointer->updateConfigurations(); \ + } \ + } \ + return this_##NAME.pointer; \ + } + +Q_GLOBAL_STATIC_QAPP_DESTRUCTION(QNetworkConfigurationManagerPrivate, connManager); + +QNetworkConfigurationManagerPrivate *qNetworkConfigurationManagerPrivate() +{ + return connManager(); +} + +/*! + \class QNetworkConfigurationManager + + \brief The QNetworkConfigurationManager class manages the network configurations provided + by the system. + + \since 4.7 + + \inmodule QtNetwork + \ingroup network + + QNetworkConfigurationManager provides access to the network configurations known to the system and + enables applications to detect the system capabilities (with regards to network sessions) at runtime. + + A QNetworkConfiguration abstracts a set of configuration options describing how a + network interface has to be configured to connect to a particular target network. + QNetworkConfigurationManager maintains and updates the global list of + QNetworkConfigurations. Applications can access and filter this list via + allConfigurations(). If a new configuration is added or an existing one is removed or changed + the configurationAdded(), configurationRemoved() and configurationChanged() signals are emitted + respectively. + + The defaultConfiguration() can be used when intending to immediately create a new + network session without caring about the particular configuration. It returns + a \l QNetworkConfiguration::Discovered configuration. If there are not any + discovered ones an invalid configuration is returned. + + Some configuration updates may require some time to perform updates. A WLAN scan is + such an example. Unless the platform performs internal updates it may be required to + manually trigger configuration updates via QNetworkConfigurationManager::updateConfigurations(). + The completion of the update process is indicted by emitting the updateCompleted() + signal. The update process ensures that every existing QNetworkConfiguration instance + is updated. There is no need to ask for a renewed configuration list via allConfigurations(). + + \sa QNetworkConfiguration +*/ + +/*! + \fn void QNetworkConfigurationManager::configurationAdded(const QNetworkConfiguration &config) + + This signal is emitted whenever a new network configuration is added to the system. The new + configuration is specified by \a config. +*/ + +/*! + \fn void QNetworkConfigurationManager::configurationRemoved(const QNetworkConfiguration &config) + + This signal is emitted when a configuration is about to be removed from the system. The removed + \a configuration is invalid but retains name and identifier. +*/ + +/*! + \fn void QNetworkConfigurationManager::updateCompleted() + + This signal is emitted when the configuration update has been completed. Such an update can + be initiated via \l updateConfigurations(). +*/ + +/*! \fn void QNetworkConfigurationManager::configurationChanged(const QNetworkConfiguration &config) + + This signal is emitted when the \l {QNetworkConfiguration::state()}{state} of \a config changes. +*/ + +/*! + \fn void QNetworkConfigurationManager::onlineStateChanged(bool isOnline) + + This signal is emitted when the device changes from online to offline mode or vice versa. + \a isOnline represents the new state of the device. + + The state is considered to be online for as long as + \l{allConfigurations()}{allConfigurations}(QNetworkConfiguration::Active) returns a list with + at least one entry. +*/ + +/*! + \enum QNetworkConfigurationManager::Capability + + Specifies the system capabilities of the bearer API. The possible values are: + + \value CanStartAndStopInterfaces Network sessions and their underlying access points can be + started and stopped. If this flag is not set QNetworkSession + can only monitor but not influence the state of access points. + On some platforms this feature may require elevated user + permissions. This option is platform specific and may not + always be available. + \value DirectConnectionRouting Network sessions and their sockets can be bound to a + particular network interface. Any packet that passes through + the socket goes to the specified network interface and thus + disregards standard routing table entries. This may be useful + when two interfaces can reach overlapping IP ranges or an + application has specific needs in regards to target networks. + This option is platform specific and may not always be + available. + \value SystemSessionSupport If this flag is set the underlying platform ensures that a + network interface is not shut down until the last network + session has been \l{QNetworkSession::close()}{closed()}. This + works across multiple processes. If the platform session + support is missing this API can only ensure the above behavior + for network sessions within the same process. + In general mobile platforms (such as Symbian/S60) have such + support whereas most desktop platform lack this capability. + \value ApplicationLevelRoaming The system gives applications control over the systems roaming + behavior. Applications can initiate roaming (in case the + current link is not suitable) and are consulted if the system + has identified a more suitable access point. + \value ForcedRoaming The system disconnects an existing access point and reconnects + via a more suitable one. The application does not have any + control over this process and has to reconnect its active + sockets. + \value DataStatistics If this flag is set QNetworkSession can provide statistics + about transmitted and received data. + \value NetworkSessionRequired If this flag is set the platform requires that a network + session is created before network operations can be performed. +*/ + +/*! + Constructs a QNetworkConfigurationManager with the given \a parent. + + Note that to ensure a valid list of current configurations immediately available, updating + is done during construction which causes some delay. +*/ +QNetworkConfigurationManager::QNetworkConfigurationManager(QObject *parent) + : QObject(parent) +{ + QNetworkConfigurationManagerPrivate *priv = qNetworkConfigurationManagerPrivate(); + + connect(priv, SIGNAL(configurationAdded(QNetworkConfiguration)), + this, SIGNAL(configurationAdded(QNetworkConfiguration))); + connect(priv, SIGNAL(configurationRemoved(QNetworkConfiguration)), + this, SIGNAL(configurationRemoved(QNetworkConfiguration))); + connect(priv, SIGNAL(configurationChanged(QNetworkConfiguration)), + this, SIGNAL(configurationChanged(QNetworkConfiguration))); + connect(priv, SIGNAL(onlineStateChanged(bool)), + this, SIGNAL(onlineStateChanged(bool))); + connect(priv, SIGNAL(configurationUpdateComplete()), + this, SIGNAL(updateCompleted())); + + priv->enablePolling(); +} + +/*! + Frees the resources associated with the QNetworkConfigurationManager object. +*/ +QNetworkConfigurationManager::~QNetworkConfigurationManager() +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + priv->disablePolling(); +} + + +/*! + Returns the default configuration to be used. This function always returns a discovered + configuration; otherwise an invalid configuration. + + In some cases it may be required to call updateConfigurations() and wait for the + updateCompleted() signal before calling this function. + + \sa allConfigurations() +*/ +QNetworkConfiguration QNetworkConfigurationManager::defaultConfiguration() const +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + return priv->defaultConfiguration(); + + return QNetworkConfiguration(); +} + +/*! + Returns the list of configurations which comply with the given \a filter. + + By default this function returns all (defined and undefined) configurations. + + A wireless network with a particular SSID may only be accessible in a + certain area despite the fact that the system has a valid configuration + for it. Therefore the filter flag may be used to limit the list to + discovered and possibly connected configurations only. + + If \a filter is set to zero this function returns all possible configurations. + + Note that this function returns the states for all configurations as they are known at + the time of this function call. If for instance a configuration of type WLAN is defined + the system may have to perform a WLAN scan in order to determine whether it is + actually available. To obtain the most accurate state updateConfigurations() should + be used to update each configuration's state. Note that such an update may require + some time. It's completion is signalled by updateCompleted(). In the absence of a + configuration update this function returns the best estimate at the time of the call. + Therefore, if WLAN configurations are of interest, it is recommended that + updateConfigurations() is called once after QNetworkConfigurationManager + instantiation (WLAN scans are too time consuming to perform in constructor). + After this the data is kept automatically up-to-date as the system reports + any changes. +*/ +QList<QNetworkConfiguration> QNetworkConfigurationManager::allConfigurations(QNetworkConfiguration::StateFlags filter) const +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + return priv->allConfigurations(filter); + + return QList<QNetworkConfiguration>(); +} + +/*! + Returns the QNetworkConfiguration for \a identifier; otherwise returns an + invalid QNetworkConfiguration. + + \sa QNetworkConfiguration::identifier() +*/ +QNetworkConfiguration QNetworkConfigurationManager::configurationFromIdentifier(const QString &identifier) const +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + return priv->configurationFromIdentifier(identifier); + + return QNetworkConfiguration(); +} + +/*! + Returns true if the system is considered to be connected to another device via an active + network interface; otherwise returns false. + + This is equivalent to the following code snippet: + + \snippet doc/src/snippets/code/src_network_bearer_qnetworkconfigmanager.cpp 0 + + \sa onlineStateChanged() +*/ +bool QNetworkConfigurationManager::isOnline() const +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + return priv->isOnline(); + + return false; +} + +/*! + Returns the capabilities supported by the current platform. +*/ +QNetworkConfigurationManager::Capabilities QNetworkConfigurationManager::capabilities() const +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + return priv->capabilities(); + + return QNetworkConfigurationManager::Capabilities(0); +} + +/*! + Initiates an update of all configurations. This may be used to initiate WLAN scans or other + time consuming updates which may be required to obtain the correct state for configurations. + + This call is asynchronous. On completion of this update the updateCompleted() signal is + emitted. If new configurations are discovered or old ones were removed or changed the update + process may trigger the emission of one or multiple configurationAdded(), + configurationRemoved() and configurationChanged() signals. + + If a configuration state changes as a result of this update all existing QNetworkConfiguration + instances are updated automatically. + + \sa allConfigurations() +*/ +void QNetworkConfigurationManager::updateConfigurations() +{ + QNetworkConfigurationManagerPrivate *priv = connManager(); + if (priv) + priv->performAsyncConfigurationUpdate(); +} + +#include "moc_qnetworkconfigmanager.cpp" + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT diff --git a/src/network/bearer/qnetworkconfigmanager.h b/src/network/bearer/qnetworkconfigmanager.h new file mode 100644 index 0000000000..565156cbc5 --- /dev/null +++ b/src/network/bearer/qnetworkconfigmanager.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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 QNETWORKCONFIGURATIONMANAGER_H +#define QNETWORKCONFIGURATIONMANAGER_H + +#ifdef QT_MOBILITY_BEARER +# include "qmobilityglobal.h" +#endif + +#include <QtCore/qobject.h> +#include <QtNetwork/qnetworkconfiguration.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_HEADER + +#ifndef QT_MOBILITY_BEARER +QT_BEGIN_NAMESPACE +#define QNetworkConfigurationManagerExport Q_NETWORK_EXPORT +QT_MODULE(Network) +#else +QTM_BEGIN_NAMESPACE +#define QNetworkConfigurationManagerExport Q_BEARER_EXPORT +#endif + +class QNetworkConfigurationManagerPrivate; +class QNetworkConfigurationManagerExport QNetworkConfigurationManager : public QObject +{ + Q_OBJECT + +public: + enum Capability { + CanStartAndStopInterfaces = 0x00000001, + DirectConnectionRouting = 0x00000002, + SystemSessionSupport = 0x00000004, + ApplicationLevelRoaming = 0x00000008, + ForcedRoaming = 0x00000010, + DataStatistics = 0x00000020, + NetworkSessionRequired = 0x00000040 + }; + + Q_DECLARE_FLAGS(Capabilities, Capability) + + explicit QNetworkConfigurationManager(QObject *parent = 0); + virtual ~QNetworkConfigurationManager(); + + QNetworkConfigurationManager::Capabilities capabilities() const; + + QNetworkConfiguration defaultConfiguration() const; + QList<QNetworkConfiguration> allConfigurations(QNetworkConfiguration::StateFlags flags = 0) const; + QNetworkConfiguration configurationFromIdentifier(const QString &identifier) const; + + bool isOnline() const; + +public Q_SLOTS: + void updateConfigurations(); + +Q_SIGNALS: + void configurationAdded(const QNetworkConfiguration &config); + void configurationRemoved(const QNetworkConfiguration &config); + void configurationChanged(const QNetworkConfiguration &config); + void onlineStateChanged(bool isOnline); + void updateCompleted(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QNetworkConfigurationManager::Capabilities) + +#ifndef QT_MOBILITY_BEARER +QT_END_NAMESPACE +#else +QTM_END_NAMESPACE +#endif + +QT_END_HEADER + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QNETWORKCONFIGURATIONMANAGER_H diff --git a/src/network/bearer/qnetworkconfigmanager_p.cpp b/src/network/bearer/qnetworkconfigmanager_p.cpp new file mode 100644 index 0000000000..c108ad34cf --- /dev/null +++ b/src/network/bearer/qnetworkconfigmanager_p.cpp @@ -0,0 +1,484 @@ +/**************************************************************************** +** +** 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 "qnetworkconfigmanager_p.h" +#include "qbearerplugin_p.h" + +#include <QtCore/private/qfactoryloader_p.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qtimer.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qthread.h> +#include <QtCore/private/qcoreapplication_p.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QBearerEngineFactoryInterface_iid, QLatin1String("/bearer"))) +#endif + +QNetworkConfigurationManagerPrivate::QNetworkConfigurationManagerPrivate() + : QObject(), mutex(QMutex::Recursive), forcedPolling(0), firstUpdate(true) +{ + qRegisterMetaType<QNetworkConfiguration>("QNetworkConfiguration"); + qRegisterMetaType<QNetworkConfigurationPrivatePointer>("QNetworkConfigurationPrivatePointer"); +} + +QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() +{ + QMutexLocker locker(&mutex); + + qDeleteAll(sessionEngines); +} + +QNetworkConfiguration QNetworkConfigurationManagerPrivate::defaultConfiguration() const +{ + QMutexLocker locker(&mutex); + + foreach (QBearerEngine *engine, sessionEngines) { + QNetworkConfigurationPrivatePointer ptr = engine->defaultConfiguration(); + if (ptr) { + QNetworkConfiguration config; + config.d = ptr; + return config; + } + } + + // Engines don't have a default configuration. + + // Return first active snap + QNetworkConfigurationPrivatePointer defaultConfiguration; + + foreach (QBearerEngine *engine, sessionEngines) { + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator it; + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator end; + + QMutexLocker locker(&engine->mutex); + + for (it = engine->snapConfigurations.begin(), + end = engine->snapConfigurations.end(); it != end; ++it) { + QNetworkConfigurationPrivatePointer ptr = it.value(); + + QMutexLocker configLocker(&ptr->mutex); + + if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) { + QNetworkConfiguration config; + config.d = ptr; + return config; + } else if (!defaultConfiguration) { + if ((ptr->state & QNetworkConfiguration::Discovered) == QNetworkConfiguration::Discovered) + defaultConfiguration = ptr; + } + } + } + + // No Active SNAPs return first Discovered SNAP. + if (defaultConfiguration) { + QNetworkConfiguration config; + config.d = defaultConfiguration; + return config; + } + + /* + No Active or Discovered SNAPs, find the perferred access point. + The following priority order is used: + + 1. Active Ethernet + 2. Active WLAN + 3. Active Other + 4. Discovered Ethernet + 5. Discovered WLAN + 6. Discovered Other + */ + + foreach (QBearerEngine *engine, sessionEngines) { + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator it; + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator end; + + QMutexLocker locker(&engine->mutex); + + for (it = engine->accessPointConfigurations.begin(), + end = engine->accessPointConfigurations.end(); it != end; ++it) { + QNetworkConfigurationPrivatePointer ptr = it.value(); + + QMutexLocker configLocker(&ptr->mutex); + QNetworkConfiguration::BearerType bearerType = ptr->bearerType; + + if ((ptr->state & QNetworkConfiguration::Discovered) == QNetworkConfiguration::Discovered) { + if (!defaultConfiguration) { + defaultConfiguration = ptr; + } else { + QMutexLocker defaultConfigLocker(&defaultConfiguration->mutex); + + if (defaultConfiguration->state == ptr->state) { + switch (defaultConfiguration->bearerType) { + case QNetworkConfiguration::BearerEthernet: + // do nothing + break; + case QNetworkConfiguration::BearerWLAN: + // Ethernet beats WLAN + defaultConfiguration = ptr; + break; + default: + // Ethernet and WLAN beats other + if (bearerType == QNetworkConfiguration::BearerEthernet || + bearerType == QNetworkConfiguration::BearerWLAN) { + defaultConfiguration = ptr; + } + } + } else { + // active beats discovered + if ((defaultConfiguration->state & QNetworkConfiguration::Active) != + QNetworkConfiguration::Active) { + defaultConfiguration = ptr; + } + } + } + } + } + } + + // No Active InternetAccessPoint return first Discovered InternetAccessPoint. + if (defaultConfiguration) { + QNetworkConfiguration config; + config.d = defaultConfiguration; + return config; + } + + return QNetworkConfiguration(); +} + +QList<QNetworkConfiguration> QNetworkConfigurationManagerPrivate::allConfigurations(QNetworkConfiguration::StateFlags filter) const +{ + QList<QNetworkConfiguration> result; + + QMutexLocker locker(&mutex); + + foreach (QBearerEngine *engine, sessionEngines) { + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator it; + QHash<QString, QNetworkConfigurationPrivatePointer>::Iterator end; + + QMutexLocker locker(&engine->mutex); + + //find all InternetAccessPoints + for (it = engine->accessPointConfigurations.begin(), + end = engine->accessPointConfigurations.end(); it != end; ++it) { + QNetworkConfigurationPrivatePointer ptr = it.value(); + + QMutexLocker configLocker(&ptr->mutex); + + if ((ptr->state & filter) == filter) { + QNetworkConfiguration pt; + pt.d = ptr; + result << pt; + } + } + + //find all service networks + for (it = engine->snapConfigurations.begin(), + end = engine->snapConfigurations.end(); it != end; ++it) { + QNetworkConfigurationPrivatePointer ptr = it.value(); + + QMutexLocker configLocker(&ptr->mutex); + + if ((ptr->state & filter) == filter) { + QNetworkConfiguration pt; + pt.d = ptr; + result << pt; + } + } + } + + return result; +} + +QNetworkConfiguration QNetworkConfigurationManagerPrivate::configurationFromIdentifier(const QString &identifier) const +{ + QNetworkConfiguration item; + + QMutexLocker locker(&mutex); + + foreach (QBearerEngine *engine, sessionEngines) { + QMutexLocker locker(&engine->mutex); + + if (engine->accessPointConfigurations.contains(identifier)) + item.d = engine->accessPointConfigurations[identifier]; + else if (engine->snapConfigurations.contains(identifier)) + item.d = engine->snapConfigurations[identifier]; + else if (engine->userChoiceConfigurations.contains(identifier)) + item.d = engine->userChoiceConfigurations[identifier]; + else + continue; + + return item; + } + + return item; +} + +bool QNetworkConfigurationManagerPrivate::isOnline() const +{ + QMutexLocker locker(&mutex); + + return !onlineConfigurations.isEmpty(); +} + +QNetworkConfigurationManager::Capabilities QNetworkConfigurationManagerPrivate::capabilities() const +{ + QMutexLocker locker(&mutex); + + QNetworkConfigurationManager::Capabilities capFlags; + + foreach (QBearerEngine *engine, sessionEngines) + capFlags |= engine->capabilities(); + + return capFlags; +} + +void QNetworkConfigurationManagerPrivate::configurationAdded(QNetworkConfigurationPrivatePointer ptr) +{ + QMutexLocker locker(&mutex); + + if (!firstUpdate) { + QNetworkConfiguration item; + item.d = ptr; + emit configurationAdded(item); + } + + ptr->mutex.lock(); + if (ptr->state == QNetworkConfiguration::Active) { + ptr->mutex.unlock(); + onlineConfigurations.insert(ptr->id); + if (!firstUpdate && onlineConfigurations.count() == 1) + emit onlineStateChanged(true); + } else { + ptr->mutex.unlock(); + } +} + +void QNetworkConfigurationManagerPrivate::configurationRemoved(QNetworkConfigurationPrivatePointer ptr) +{ + QMutexLocker locker(&mutex); + + ptr->mutex.lock(); + ptr->isValid = false; + ptr->mutex.unlock(); + + if (!firstUpdate) { + QNetworkConfiguration item; + item.d = ptr; + emit configurationRemoved(item); + } + + onlineConfigurations.remove(ptr->id); + if (!firstUpdate && onlineConfigurations.isEmpty()) + emit onlineStateChanged(false); +} + +void QNetworkConfigurationManagerPrivate::configurationChanged(QNetworkConfigurationPrivatePointer ptr) +{ + QMutexLocker locker(&mutex); + + if (!firstUpdate) { + QNetworkConfiguration item; + item.d = ptr; + emit configurationChanged(item); + } + + bool previous = !onlineConfigurations.isEmpty(); + + ptr->mutex.lock(); + if (ptr->state == QNetworkConfiguration::Active) + onlineConfigurations.insert(ptr->id); + else + onlineConfigurations.remove(ptr->id); + ptr->mutex.unlock(); + + bool online = !onlineConfigurations.isEmpty(); + + if (!firstUpdate && online != previous) + emit onlineStateChanged(online); +} + +void QNetworkConfigurationManagerPrivate::updateConfigurations() +{ + QMutexLocker locker(&mutex); + + if (firstUpdate) { + if (qobject_cast<QBearerEngine *>(sender())) + return; + + if (thread() != QCoreApplicationPrivate::mainThread()) { + if (thread() != QThread::currentThread()) + return; + + moveToThread(QCoreApplicationPrivate::mainThread()); + } + + updating = false; + +#ifndef QT_NO_LIBRARY + QBearerEngine *generic = 0; + + QFactoryLoader *l = loader(); + foreach (const QString &key, l->keys()) { + QBearerEnginePlugin *plugin = qobject_cast<QBearerEnginePlugin *>(l->instance(key)); + if (plugin) { + QBearerEngine *engine = plugin->create(key); + if (!engine) + continue; + + if (key == QLatin1String("generic")) + generic = engine; + else + sessionEngines.append(engine); + + engine->moveToThread(QCoreApplicationPrivate::mainThread()); + + connect(engine, SIGNAL(updateCompleted()), + this, SLOT(updateConfigurations())); + connect(engine, SIGNAL(configurationAdded(QNetworkConfigurationPrivatePointer)), + this, SLOT(configurationAdded(QNetworkConfigurationPrivatePointer))); + connect(engine, SIGNAL(configurationRemoved(QNetworkConfigurationPrivatePointer)), + this, SLOT(configurationRemoved(QNetworkConfigurationPrivatePointer))); + connect(engine, SIGNAL(configurationChanged(QNetworkConfigurationPrivatePointer)), + this, SLOT(configurationChanged(QNetworkConfigurationPrivatePointer))); + + QMetaObject::invokeMethod(engine, "initialize"); + } + } + + if (generic) + sessionEngines.append(generic); +#endif // QT_NO_LIBRARY + } + + QBearerEngine *engine = qobject_cast<QBearerEngine *>(sender()); + if (engine && !updatingEngines.isEmpty()) + updatingEngines.remove(engine); + + if (updating && updatingEngines.isEmpty()) { + updating = false; + emit configurationUpdateComplete(); + } + + if (engine && !pollingEngines.isEmpty()) { + pollingEngines.remove(engine); + if (pollingEngines.isEmpty()) + startPolling(); + } + + if (firstUpdate) + firstUpdate = false; +} + +void QNetworkConfigurationManagerPrivate::performAsyncConfigurationUpdate() +{ + QMutexLocker locker(&mutex); + + if (sessionEngines.isEmpty()) { + emit configurationUpdateComplete(); + return; + } + + updating = true; + + foreach (QBearerEngine *engine, sessionEngines) { + updatingEngines.insert(engine); + QMetaObject::invokeMethod(engine, "requestUpdate"); + } +} + +QList<QBearerEngine *> QNetworkConfigurationManagerPrivate::engines() const +{ + QMutexLocker locker(&mutex); + + return sessionEngines; +} + +void QNetworkConfigurationManagerPrivate::startPolling() +{ + QMutexLocker locker(&mutex); + + foreach (QBearerEngine *engine, sessionEngines) { + if (engine->requiresPolling() && (forcedPolling || engine->configurationsInUse())) { + QTimer::singleShot(10000, this, SLOT(pollEngines())); + break; + } + } +} + +void QNetworkConfigurationManagerPrivate::pollEngines() +{ + QMutexLocker locker(&mutex); + + foreach (QBearerEngine *engine, sessionEngines) { + if (engine->requiresPolling() && (forcedPolling || engine->configurationsInUse())) { + pollingEngines.insert(engine); + QMetaObject::invokeMethod(engine, "requestUpdate"); + } + } +} + +void QNetworkConfigurationManagerPrivate::enablePolling() +{ + QMutexLocker locker(&mutex); + + ++forcedPolling; + + if (forcedPolling == 1) + startPolling(); +} + +void QNetworkConfigurationManagerPrivate::disablePolling() +{ + QMutexLocker locker(&mutex); + + --forcedPolling; +} + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT diff --git a/src/network/bearer/qnetworkconfigmanager_p.h b/src/network/bearer/qnetworkconfigmanager_p.h new file mode 100644 index 0000000000..81f38c5183 --- /dev/null +++ b/src/network/bearer/qnetworkconfigmanager_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** 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 QNETWORKCONFIGURATIONMANAGERPRIVATE_H +#define QNETWORKCONFIGURATIONMANAGERPRIVATE_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 "qnetworkconfigmanager.h" +#include "qnetworkconfiguration_p.h" + +#include <QtCore/qmutex.h> +#include <QtCore/qset.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +class QBearerEngine; + +class Q_NETWORK_EXPORT QNetworkConfigurationManagerPrivate : public QObject +{ + Q_OBJECT + +public: + QNetworkConfigurationManagerPrivate(); + virtual ~QNetworkConfigurationManagerPrivate(); + + QNetworkConfiguration defaultConfiguration() const; + QList<QNetworkConfiguration> allConfigurations(QNetworkConfiguration::StateFlags filter) const; + QNetworkConfiguration configurationFromIdentifier(const QString &identifier) const; + + bool isOnline() const; + + QNetworkConfigurationManager::Capabilities capabilities() const; + + void performAsyncConfigurationUpdate(); + + QList<QBearerEngine *> engines() const; + + void enablePolling(); + void disablePolling(); + +public Q_SLOTS: + void updateConfigurations(); + +Q_SIGNALS: + void configurationAdded(const QNetworkConfiguration &config); + void configurationRemoved(const QNetworkConfiguration &config); + void configurationChanged(const QNetworkConfiguration &config); + void configurationUpdateComplete(); + void onlineStateChanged(bool isOnline); + +private Q_SLOTS: + void configurationAdded(QNetworkConfigurationPrivatePointer ptr); + void configurationRemoved(QNetworkConfigurationPrivatePointer ptr); + void configurationChanged(QNetworkConfigurationPrivatePointer ptr); + + void pollEngines(); + +private: + void startPolling(); + +private: + mutable QMutex mutex; + + QList<QBearerEngine *> sessionEngines; + + QSet<QString> onlineConfigurations; + + QSet<QBearerEngine *> pollingEngines; + QSet<QBearerEngine *> updatingEngines; + int forcedPolling; + bool updating; + + bool firstUpdate; +}; + +Q_NETWORK_EXPORT QNetworkConfigurationManagerPrivate *qNetworkConfigurationManagerPrivate(); + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QNETWORKCONFIGURATIONMANAGERPRIVATE_H diff --git a/src/network/bearer/qnetworkconfiguration.cpp b/src/network/bearer/qnetworkconfiguration.cpp new file mode 100644 index 0000000000..4cc3099b17 --- /dev/null +++ b/src/network/bearer/qnetworkconfiguration.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** 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 "qnetworkconfiguration.h" +#include "qnetworkconfiguration_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QNetworkConfiguration + + \brief The QNetworkConfiguration class provides an abstraction of one or more access point configurations. + + \since 4.7 + + \inmodule QtNetwork + \ingroup network + + QNetworkConfiguration encapsulates a single access point or service network. + In most cases a single access point configuration can be mapped to one network + interface. However a single network interface may not always map to only one + access point configuration. Multiple configurations for the same + network device may enable multiple access points. An example + device that could exhibit such a configuration might be a + Smartphone which allows the user to manage multiple WLAN + configurations while the device itself has only one WLAN network device. + + The QNetworkConfiguration also supports the concept of service networks. + This concept allows the grouping of multiple access point configurations + into one entity. Such a group is called service network and can be + beneficial in cases whereby a network session to a + particular destination network is required (e.g. a company network). + When using a service network the user doesn't usually care which one of the + connectivity options is chosen (e.g. corporate WLAN or VPN via GPRS) + as long as he can reach the company's target server. Depending + on the current position and time some of the access points that make + up the service network may not even be available. Furthermore + automated access point roaming can be enabled which enables the device + to change the network interface configuration dynamically while maintaining + the applications connection to the target network. It allows adaption + to the changing environment and may enable optimization with regards to + cost, speed or other network parameters. + + Special configurations of type UserChoice provide a placeholder configuration which is + resolved to an actual network configuration by the platform when a + \l {QNetworkSession}{session} is \l {QNetworkSession::open()}{opened}. Not all platforms + support the concept of a user choice configuration. + + \section1 Configuration states + + The list of available configurations can be obtained via + QNetworkConfigurationManager::allConfigurations(). A configuration can have + multiple states. The \l Defined configuration state indicates that the configuration + is stored on the device. However the configuration is not yet ready to be activated + as e.g. a WLAN may not be available at the current time. + + The \l Discovered state implies that the configuration is \l Defined and + the outside conditions are such that the configuration can be used immediately + to open a new network session. An example of such an outside condition may be + that the Ethernet cable is actually connected to the device or that the WLAN + with the specified SSID is in range. + + The \l Active state implies that the configuration is \l Discovered. A configuration + in this state is currently being used by an application. The underlying network + interface has a valid IP configuration and can transfer IP packets between the + device and the target network. + + The \l Undefined state indicates that the system has knowledge of possible target + networks but cannot actually use that knowledge to connect to it. An example + for such a state could be an encrypted WLAN that has been discovered + but the user hasn't actually saved a configuration including the required password + which would allow the device to connect to it. + + Depending on the type of configuration some states are transient in nature. A GPRS/UMTS + connection may almost always be \l Discovered if the GSM/UMTS network is available. + However if the GSM/UMTS network looses the connection the associated configuration may change its state + from \l Discovered to \l Defined as well. A similar use case might be triggered by + WLAN availability. QNetworkConfigurationManager::updateConfigurations() can be used to + manually trigger updates of states. Note that some platforms do not require such updates + as they implicitly change the state once it has been discovered. If the state of a + configuration changes all related QNetworkConfiguration instances change their state automatically. + + \sa QNetworkSession, QNetworkConfigurationManager +*/ + +/*! + \enum QNetworkConfiguration::Type + + This enum describes the type of configuration. + + \value InternetAccessPoint The configuration specifies the details for a single access point. + Note that configurations of type InternetAccessPoint may be part + of other QNetworkConfigurations of type ServiceNetwork. + \value ServiceNetwork The configuration is based on a group of QNetworkConfigurations of + type InternetAccessPoint. All group members can reach the same + target network. This type of configuration is a mandatory + requirement for roaming enabled network sessions. On some + platforms this form of configuration may also be called Service + Network Access Point (SNAP). + \value UserChoice The configuration is a placeholder which will be resolved to an + actual configuration by the platform when a session is opened. Depending + on the platform the selection may generate a popup dialog asking the user + for his preferred choice. + \value Invalid The configuration is invalid. +*/ + +/*! + \enum QNetworkConfiguration::StateFlag + + Specifies the configuration states. + + \value Undefined This state is used for transient configurations such as newly discovered + WLANs for which the user has not actually created a configuration yet. + \value Defined Defined configurations are known to the system but are not immediately + usable (e.g. a configured WLAN is not within range or the Ethernet cable + is currently not plugged into the machine). + \value Discovered A discovered configuration can be immediately used to create a new + QNetworkSession. An example of a discovered configuration could be a WLAN + which is within in range. If the device moves out of range the discovered + flag is dropped. A second example is a GPRS configuration which generally + remains discovered for as long as the device has network coverage. A + configuration that has this state is also in state + QNetworkConfiguration::Defined. If the configuration is a service network + this flag is set if at least one of the underlying access points + configurations has the Discovered state. + \value Active The configuration is currently used by an open network session + (see \l QNetworkSession::isOpen()). However this does not mean that the + current process is the entity that created the open session. It merely + indicates that if a new QNetworkSession were to be constructed based on + this configuration \l QNetworkSession::state() would return + \l QNetworkSession::Connected. This state implies the + QNetworkConfiguration::Discovered state. +*/ + +/*! + \enum QNetworkConfiguration::Purpose + + Specifies the purpose of the configuration. + + \value UnknownPurpose The configuration doesn't specify any purpose. This is the default value. + \value PublicPurpose The configuration can be used for general purpose internet access. + \value PrivatePurpose The configuration is suitable to access a private network such as an office Intranet. + \value ServiceSpecificPurpose The configuration can be used for operator specific services (e.g. + receiving MMS messages or content streaming). +*/ + +/*! + \enum QNetworkConfiguration::BearerType + + Specifies the type of bearer used by a configuration. + + \value BearerUnknown The type of bearer is unknown or unspecified. The bearerTypeName() + function may return additional information. + \value BearerEthernet The configuration is for an Ethernet interfaces. + \value BearerWLAN The configuration is for a Wireless LAN interface. + \value Bearer2G The configuration is for a CSD, GPRS, HSCSD, EDGE or cdmaOne interface. + \value BearerCDMA2000 The configuration is for CDMA interface. + \value BearerWCDMA The configuration is for W-CDMA/UMTS interface. + \value BearerHSPA The configuration is for High Speed Packet Access (HSPA) interface. + \value BearerBluetooth The configuration is for a Bluetooth interface. + \value BearerWiMAX The configuration is for a WiMAX interface. +*/ + +/*! + Constructs an invalid configuration object. + + \sa isValid() +*/ +QNetworkConfiguration::QNetworkConfiguration() + : d(0) +{ +} + +/*! + Creates a copy of the QNetworkConfiguration object contained in \a other. +*/ +QNetworkConfiguration::QNetworkConfiguration(const QNetworkConfiguration &other) + : d(other.d) +{ +} + +/*! + Frees the resources associated with the QNetworkConfiguration object. +*/ +QNetworkConfiguration::~QNetworkConfiguration() +{ +} + +/*! + Copies the content of the QNetworkConfiguration object contained in \a other into this one. +*/ +QNetworkConfiguration &QNetworkConfiguration::operator=(const QNetworkConfiguration &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true, if this configuration is the same as the \a other + configuration given; otherwise returns false. +*/ +bool QNetworkConfiguration::operator==(const QNetworkConfiguration &other) const +{ + return (d == other.d); +} + +/*! + \fn bool QNetworkConfiguration::operator!=(const QNetworkConfiguration &other) const + + Returns true if this configuration is not the same as the \a other + configuration given; otherwise returns false. +*/ + +/*! + Returns the user visible name of this configuration. + + The name may either be the name of the underlying access point or the + name for service network that this configuration represents. +*/ +QString QNetworkConfiguration::name() const +{ + if (!d) + return QString(); + + QMutexLocker locker(&d->mutex); + return d->name; +} + +/*! + Returns the unique and platform specific identifier for this network configuration; + otherwise an empty string. +*/ +QString QNetworkConfiguration::identifier() const +{ + if (!d) + return QString(); + + QMutexLocker locker(&d->mutex); + return d->id; +} + +/*! + Returns the type of the configuration. + + A configuration can represent a single access point configuration or + a set of access point configurations. Such a set is called service network. + A configuration that is based on a service network can potentially support + roaming of network sessions. +*/ +QNetworkConfiguration::Type QNetworkConfiguration::type() const +{ + if (!d) + return QNetworkConfiguration::Invalid; + + QMutexLocker locker(&d->mutex); + return d->type; +} + +/*! + Returns true if this QNetworkConfiguration object is valid. + A configuration may become invalid if the user deletes the configuration or + the configuration was default-constructed. + + The addition and removal of configurations can be monitored via the + QNetworkConfigurationManager. + + \sa QNetworkConfigurationManager +*/ +bool QNetworkConfiguration::isValid() const +{ + if (!d) + return false; + + QMutexLocker locker(&d->mutex); + return d->isValid; +} + +/*! + Returns the current state of the configuration. +*/ +QNetworkConfiguration::StateFlags QNetworkConfiguration::state() const +{ + if (!d) + return QNetworkConfiguration::Undefined; + + QMutexLocker locker(&d->mutex); + return d->state; +} + +/*! + Returns the purpose of this configuration. + + The purpose field may be used to programmatically determine the + purpose of a configuration. Such information is usually part of the + access point or service network meta data. +*/ +QNetworkConfiguration::Purpose QNetworkConfiguration::purpose() const +{ + if (!d) + return QNetworkConfiguration::UnknownPurpose; + + QMutexLocker locker(&d->mutex); + return d->purpose; +} + +/*! + Returns true if this configuration supports roaming; otherwise false. +*/ +bool QNetworkConfiguration::isRoamingAvailable() const +{ + if (!d) + return false; + + QMutexLocker locker(&d->mutex); + return d->roamingSupported; +} + +/*! + Returns all sub configurations of this network configuration in priority order. The first sub + configuration in the list has the highest priority. + + Only network configurations of type \l ServiceNetwork can have children. Otherwise this + function returns an empty list. +*/ +QList<QNetworkConfiguration> QNetworkConfiguration::children() const +{ + QList<QNetworkConfiguration> results; + + if (!d) + return results; + + QMutexLocker locker(&d->mutex); + + if (d->type != QNetworkConfiguration::ServiceNetwork || !d->isValid) + return results; + + QMutableMapIterator<unsigned int, QNetworkConfigurationPrivatePointer> i(d->serviceNetworkMembers); + while (i.hasNext()) { + i.next(); + + QNetworkConfigurationPrivatePointer p = i.value(); + + //if we have an invalid member get rid of it -> was deleted earlier on + { + QMutexLocker childLocker(&p->mutex); + + if (!p->isValid) { + i.remove(); + continue; + } + } + + QNetworkConfiguration item; + item.d = p; + results << item; + } + + return results; +} + +/*! + \fn QString QNetworkConfiguration::bearerName() const + \deprecated + + This function is deprecated. It is equivalent to calling bearerTypeName(), however + bearerType() should be used in preference. +*/ + +/*! + Returns the type of bearer used by this network configuration. + + If the bearer type is \l {QNetworkConfiguration::BearerUnknown}{unknown} the bearerTypeName() + function can be used to retrieve a textural type name for the bearer. + + An invalid network configuration always returns the BearerUnknown value. +*/ +QNetworkConfiguration::BearerType QNetworkConfiguration::bearerType() const +{ + if (!isValid()) + return BearerUnknown; + + QMutexLocker locker(&d->mutex); + + return d->bearerType; +} + +/*! + Returns the type of bearer used by this network configuration as a string. + + The string is not translated and therefore can not be shown to the user. The subsequent table + shows the fixed mappings between BearerType and the bearer type name for known types. If the + BearerType is unknown this function may return additional information if it is available; + otherwise an empty string will be returned. + + \table + \header + \o BearerType + \o Value + \row + \o BearerUnknown + \o + \o The session is based on an unknown or unspecified bearer type. The value of the + string returned describes the bearer type. + \row + \o BearerEthernet + \o Ethernet + \row + \o BearerWLAN + \o WLAN + \row + \o Bearer2G + \o 2G + \row + \o BearerCDMA2000 + \o CDMA2000 + \row + \o BearerWCDMA + \o WCDMA + \row + \o BearerHSPA + \o HSPA + \row + \o BearerBluetooth + \o Bluetooth + \row + \o BearerWiMAX + \o WiMAX + \endtable + + This function returns an empty string if this is an invalid configuration, a network + configuration of type \l QNetworkConfiguration::ServiceNetwork or + \l QNetworkConfiguration::UserChoice. + + \sa bearerType() +*/ +QString QNetworkConfiguration::bearerTypeName() const +{ + if (!isValid()) + return QString(); + + QMutexLocker locker(&d->mutex); + + if (d->type == QNetworkConfiguration::ServiceNetwork || + d->type == QNetworkConfiguration::UserChoice) + return QString(); + + switch (d->bearerType) { + case BearerUnknown: + return d->bearerTypeName(); + case BearerEthernet: + return QLatin1String("Ethernet"); + case BearerWLAN: + return QLatin1String("WLAN"); + case Bearer2G: + return QLatin1String("2G"); + case BearerCDMA2000: + return QLatin1String("CDMA2000"); + case BearerWCDMA: + return QLatin1String("WCDMA"); + case BearerHSPA: + return QLatin1String("HSPA"); + case BearerBluetooth: + return QLatin1String("Bluetooth"); + case BearerWiMAX: + return QLatin1String("WiMAX"); + } + + return QLatin1String("Unknown"); +} + +QT_END_NAMESPACE diff --git a/src/network/bearer/qnetworkconfiguration.h b/src/network/bearer/qnetworkconfiguration.h new file mode 100644 index 0000000000..2e8dadda25 --- /dev/null +++ b/src/network/bearer/qnetworkconfiguration.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** 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 QNETWORKCONFIGURATION_H +#define QNETWORKCONFIGURATION_H + +#ifndef QT_MOBILITY_BEARER +# include <QtCore/qglobal.h> +#else +# include "qmobilityglobal.h" +#endif + +#include <QtCore/qshareddata.h> +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> + +#if defined(Q_OS_WIN) && defined(interface) +#undef interface +#endif + +QT_BEGIN_HEADER + +#ifndef QT_MOBILITY_BEARER +QT_BEGIN_NAMESPACE +QT_MODULE(Network) +#define QNetworkConfigurationExport Q_NETWORK_EXPORT +#else +QTM_BEGIN_NAMESPACE +#define QNetworkConfigurationExport Q_BEARER_EXPORT +#endif + +class QNetworkConfigurationPrivate; +class QNetworkConfigurationExport QNetworkConfiguration +{ +public: + QNetworkConfiguration(); + QNetworkConfiguration(const QNetworkConfiguration& other); + QNetworkConfiguration &operator=(const QNetworkConfiguration &other); + ~QNetworkConfiguration(); + + bool operator==(const QNetworkConfiguration &other) const; + inline bool operator!=(const QNetworkConfiguration &other) const + { return !operator==(other); } + + enum Type { + InternetAccessPoint = 0, + ServiceNetwork, + UserChoice, + Invalid + }; + + enum Purpose { + UnknownPurpose = 0, + PublicPurpose, + PrivatePurpose, + ServiceSpecificPurpose + }; + + enum StateFlag { + Undefined = 0x0000001, + Defined = 0x0000002, + Discovered = 0x0000006, + Active = 0x000000e + }; + Q_DECLARE_FLAGS(StateFlags, StateFlag) + +#ifndef QT_MOBILITY_BEARER + enum BearerType { + BearerUnknown, + BearerEthernet, + BearerWLAN, + Bearer2G, + BearerCDMA2000, + BearerWCDMA, + BearerHSPA, + BearerBluetooth, + BearerWiMAX + }; +#endif + + StateFlags state() const; + Type type() const; + Purpose purpose() const; + +#ifndef QT_MOBILITY_BEARER +#ifdef QT_DEPRECATED + // Required to maintain source compatibility with Qt Mobility. + QT_DEPRECATED inline QString bearerName() const { return bearerTypeName(); } +#endif + BearerType bearerType() const; + QString bearerTypeName() const; +#else + QString bearerName() const; +#endif + + QString identifier() const; + bool isRoamingAvailable() const; + QList<QNetworkConfiguration> children() const; + + QString name() const; + bool isValid() const; + +private: + friend class QNetworkConfigurationPrivate; + friend class QNetworkConfigurationManager; + friend class QNetworkConfigurationManagerPrivate; + friend class QNetworkSessionPrivate; + QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> d; +}; + +#ifndef QT_MOBILITY_BEARER +QT_END_NAMESPACE +#else +QTM_END_NAMESPACE +#endif + +QT_END_HEADER + +#endif // QNETWORKCONFIGURATION_H diff --git a/src/network/bearer/qnetworkconfiguration_p.h b/src/network/bearer/qnetworkconfiguration_p.h new file mode 100644 index 0000000000..a38bc0b7a1 --- /dev/null +++ b/src/network/bearer/qnetworkconfiguration_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 QNETWORKCONFIGURATIONPRIVATE_H +#define QNETWORKCONFIGURATIONPRIVATE_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 "qnetworkconfiguration.h" + +#include <QtCore/qshareddata.h> +#include <QtCore/qmutex.h> +#include <QtCore/qmap.h> + +QT_BEGIN_NAMESPACE + +typedef QExplicitlySharedDataPointer<QNetworkConfigurationPrivate> QNetworkConfigurationPrivatePointer; +class QNetworkConfigurationPrivate : public QSharedData +{ +public: + QNetworkConfigurationPrivate() : + mutex(QMutex::Recursive), + type(QNetworkConfiguration::Invalid), + purpose(QNetworkConfiguration::UnknownPurpose), + bearerType(QNetworkConfiguration::BearerUnknown), + isValid(false), roamingSupported(false) + {} + virtual ~QNetworkConfigurationPrivate() + { + //release pointers to member configurations + serviceNetworkMembers.clear(); + } + + virtual QString bearerTypeName() const + { + return QLatin1String("Unknown"); + } + + QMap<unsigned int, QNetworkConfigurationPrivatePointer> serviceNetworkMembers; + + mutable QMutex mutex; + + QString name; + QString id; + + QNetworkConfiguration::StateFlags state; + QNetworkConfiguration::Type type; + QNetworkConfiguration::Purpose purpose; + QNetworkConfiguration::BearerType bearerType; + + bool isValid; + bool roamingSupported; + +private: + Q_DISABLE_COPY(QNetworkConfigurationPrivate) +}; + +QT_END_NAMESPACE + +#endif // QNETWORKCONFIGURATIONPRIVATE_H diff --git a/src/network/bearer/qnetworksession.cpp b/src/network/bearer/qnetworksession.cpp new file mode 100644 index 0000000000..21e64d963e --- /dev/null +++ b/src/network/bearer/qnetworksession.cpp @@ -0,0 +1,752 @@ +/**************************************************************************** +** +** 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 "qnetworksession.h" +#include "qbearerengine_p.h" + +#include <QEventLoop> +#include <QTimer> +#include <QThread> + +#include "qnetworkconfigmanager_p.h" +#include "qnetworksession_p.h" + +#ifdef Q_OS_SYMBIAN +#include <es_sock.h> +#include <private/qcore_symbian_p.h> +#endif + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +/*! + \class QNetworkSession + + \brief The QNetworkSession class provides control over the system's access points + and enables session management for cases when multiple clients access the same access point. + + \since 4.7 + + \inmodule QtNetwork + \ingroup network + + A QNetworkSession enables control over the system's network interfaces. The session's configuration + parameter are determined via the QNetworkConfiguration object to which it is bound. Depending on the + type of the session (single access point or service network) a session may be linked to one or more + network interfaces. By means of \l{open()}{opening} and \l{close()}{closing} of network sessions + a developer can start and stop the systems network interfaces. If the configuration represents + multiple access points (see \l QNetworkConfiguration::ServiceNetwork) more advanced features such as roaming may be supported. + + QNetworkSession supports session management within the same process and depending on the platform's + capabilities may support out-of-process sessions. If the same + network configuration is used by multiple open sessions the underlying network interface is only terminated once + the last session has been closed. + + \section1 Roaming + + Applications may connect to the preferredConfigurationChanged() signal in order to + receive notifications when a more suitable access point becomes available. + In response to this signal the application must either initiate the roaming via migrate() + or ignore() the new access point. Once the session has roamed the + newConfigurationActivated() signal is emitted. The application may now test the + carrier and must either accept() or reject() it. The session will return to the previous + access point if the roaming was rejected. The subsequent state diagram depicts the required + state transitions. + + \image roaming-states.png + + Some platforms may distinguish forced roaming and application level roaming (ALR). + ALR implies that the application controls (via migrate(), ignore(), accept() and reject()) + whether a network session can roam from one access point to the next. Such control is useful + if the application maintains stateful socket connections and wants to control the transition from + one interface to the next. Forced roaming implies that the system automatically roams to the next network without + consulting the application. This has the advantage that the application can make use of roaming features + without actually being aware of it. It is expected that the application detects that the underlying + socket is broken and automatically reconnects via the new network link. + + If the platform supports both modes of roaming, an application indicates its preference + by connecting to the preferredConfigurationChanged() signal. Connecting to this signal means that + the application wants to take control over the roaming behavior and therefore implies application + level roaming. If the client does not connect to the preferredConfigurationChanged(), forced roaming + is used. If forced roaming is not supported the network session will not roam by default. + + Some applications may want to suppress any form of roaming altogether. Possible use cases may be + high priority downloads or remote services which cannot handle a roaming enabled client. Clients + can suppress roaming by connecting to the preferredConfigurationChanged() signal and answer each + signal emission with ignore(). + + \sa QNetworkConfiguration, QNetworkConfigurationManager +*/ + +/*! + \enum QNetworkSession::State + + This enum describes the connectivity state of the session. If the session is based on a + single access point configuration the state of the session is the same as the state of the + associated network interface. + + \value Invalid The session is invalid due to an invalid configuration. This may + happen due to a removed access point or a configuration that was + invalid to begin with. + \value NotAvailable The session is based on a defined but not yet discovered QNetworkConfiguration + (see \l QNetworkConfiguration::StateFlag). + \value Connecting The network session is being established. + \value Connected The network session is connected. If the current process wishes to use this session + it has to register its interest by calling open(). A network session + is considered to be ready for socket operations if it isOpen() and connected. + \value Closing The network session is in the process of being shut down. + \value Disconnected The network session is not connected. The associated QNetworkConfiguration + has the state QNetworkConfiguration::Discovered. + \value Roaming The network session is roaming from one access point to another + access point. +*/ + +/*! + \enum QNetworkSession::SessionError + + This enum describes the session errors that can occur. + + \value UnknownSessionError An unidentified error occurred. + \value SessionAbortedError The session was aborted by the user or system. + \value RoamingError The session cannot roam to a new configuration. + \value OperationNotSupportedError The operation is not supported for current configuration. + \value InvalidConfigurationError The operation cannot currently be performed for the + current configuration. +*/ + +/*! + \fn void QNetworkSession::stateChanged(QNetworkSession::State state) + + This signal is emitted whenever the state of the network session changes. + The \a state parameter is the new state. + + \sa state() +*/ + +/*! + \fn void QNetworkSession::error(QNetworkSession::SessionError error) + + This signal is emitted after an error occurred. The \a error parameter + describes the error that occurred. + + \sa error(), errorString() +*/ + +/*! + \fn void QNetworkSession::preferredConfigurationChanged(const QNetworkConfiguration &config, bool isSeamless) + + This signal is emitted when the preferred configuration/access point for the + session changes. Only sessions which are based on service network configurations + may emit this signal. \a config can be used to determine access point specific + details such as proxy settings and \a isSeamless indicates whether roaming will + break the sessions IP address. + + As a consequence to this signal the application must either start the roaming process + by calling migrate() or choose to ignore() the new access point. + + If the roaming process is non-seamless the IP address will change which means that + a socket becomes invalid. However seamless mobility can ensure that the local IP address + does not change. This is achieved by using a virtual IP address which is bound to the actual + link address. During the roaming process the virtual address is attached to the new link + address. + + Some platforms may support the concept of Forced Roaming and Application Level Roaming (ALR). + Forced roaming implies that the platform may simply roam to a new configuration without + consulting applications. It is up to the application to detect the link layer loss and reestablish + its sockets. In contrast ALR provides the opportunity to prevent the system from roaming. + If this session is based on a configuration that supports roaming the application can choose + whether it wants to be consulted (ALR use case) by connecting to this signal. For as long as this signal + connection remains the session remains registered as a roaming stakeholder; otherwise roaming will + be enforced by the platform. + + \sa migrate(), ignore(), QNetworkConfiguration::isRoamingAvailable() +*/ + +/*! + \fn void QNetworkSession::newConfigurationActivated() + + This signal is emitted once the session has roamed to the new access point. + The application may reopen its socket and test the suitability of the new network link. + Subsequently it must either accept() or reject() the new access point. + + \sa accept(), reject() +*/ + +/*! + \fn void QNetworkSession::opened() + + This signal is emitted when the network session has been opened. + + The underlying network interface will not be shut down as long as the session remains open. + Note that this feature is dependent on \l{QNetworkConfigurationManager::SystemSessionSupport}{system wide session support}. +*/ + +/*! + \fn void QNetworkSession::closed() + + This signal is emitted when the network session has been closed. +*/ + +/*! + Constructs a session based on \a connectionConfig with the given \a parent. + + \sa QNetworkConfiguration +*/ +QNetworkSession::QNetworkSession(const QNetworkConfiguration &connectionConfig, QObject *parent) + : QObject(parent), d(0) +{ + // invalid configuration + if (!connectionConfig.identifier().isEmpty()) { + foreach (QBearerEngine *engine, qNetworkConfigurationManagerPrivate()->engines()) { + if (engine->hasIdentifier(connectionConfig.identifier())) { + d = engine->createSessionBackend(); + d->q = this; + d->publicConfig = connectionConfig; + d->syncStateWithInterface(); + connect(d, SIGNAL(quitPendingWaitsForOpened()), this, SIGNAL(opened())); + connect(d, SIGNAL(error(QNetworkSession::SessionError)), + this, SIGNAL(error(QNetworkSession::SessionError))); + connect(d, SIGNAL(stateChanged(QNetworkSession::State)), + this, SIGNAL(stateChanged(QNetworkSession::State))); + connect(d, SIGNAL(closed()), this, SIGNAL(closed())); + connect(d, SIGNAL(preferredConfigurationChanged(QNetworkConfiguration,bool)), + this, SIGNAL(preferredConfigurationChanged(QNetworkConfiguration,bool))); + connect(d, SIGNAL(newConfigurationActivated()), + this, SIGNAL(newConfigurationActivated())); + break; + } + } + } + + qRegisterMetaType<QNetworkSession::State>(); + qRegisterMetaType<QNetworkSession::SessionError>(); +} + +/*! + Frees the resources associated with the QNetworkSession object. +*/ +QNetworkSession::~QNetworkSession() +{ + delete d; +} + +/*! + Creates an open session which increases the session counter on the underlying network interface. + The system will not terminate a network interface until the session reference counter reaches zero. + Therefore an open session allows an application to register its use of the interface. + + As a result of calling open() the interface will be started if it is not connected/up yet. + Some platforms may not provide support for out-of-process sessions. On such platforms the session + counter ignores any sessions held by another process. The platform capabilities can be + detected via QNetworkConfigurationManager::capabilities(). + + Note that this call is asynchronous. Depending on the outcome of this call the results can be enquired + by connecting to the stateChanged(), opened() or error() signals. + + It is not a requirement to open a session in order to monitor the underlying network interface. + + \sa close(), stop(), isOpen() +*/ +void QNetworkSession::open() +{ + if (d) + d->open(); + else + emit error(InvalidConfigurationError); +} + +/*! + Waits until the session has been opened, up to \a msecs milliseconds. If the session has been opened, this + function returns true; otherwise it returns false. In the case where it returns false, you can call error() + to determine the cause of the error. + + The following example waits up to one second for the session to be opened: + + \code + session->open(); + if (session->waitForOpened(1000)) + qDebug("Open!"); + \endcode + + If \a msecs is -1, this function will not time out. + + \sa open(), error() +*/ +bool QNetworkSession::waitForOpened(int msecs) +{ + if (!d) + return false; + + if (d->isOpen) + return true; + + if (!(d->state == Connecting || d->state == Connected)) { + return false; + } + + QEventLoop loop; + QObject::connect(d, SIGNAL(quitPendingWaitsForOpened()), &loop, SLOT(quit())); + QObject::connect(this, SIGNAL(error(QNetworkSession::SessionError)), &loop, SLOT(quit())); + + //final call + if (msecs >= 0) + QTimer::singleShot(msecs, &loop, SLOT(quit())); + + // enter the event loop and wait for opened/error/timeout + loop.exec(QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); + + return d->isOpen; +} + +/*! + Decreases the session counter on the associated network configuration. If the session counter reaches zero + the active network interface is shut down. This also means that state() will only change from \l Connected to + \l Disconnected if the current session was the last open session. + + If the platform does not support out-of-process sessions calling this function does not stop the + interface. In this case \l{stop()} has to be used to force a shut down. + The platform capabilities can be detected via QNetworkConfigurationManager::capabilities(). + + Note that this call is asynchronous. Depending on the outcome of this call the results can be enquired + by connecting to the stateChanged(), opened() or error() signals. + + \sa open(), stop(), isOpen() +*/ +void QNetworkSession::close() +{ + if (d) + d->close(); +} + +/*! + Invalidates all open sessions against the network interface and therefore stops the + underlying network interface. This function always changes the session's state() flag to + \l Disconnected. + + On Symbian platform, a 'NetworkControl' capability is required for + full interface-level stop (without the capability, only the current session is stopped). + + \sa open(), close() +*/ +void QNetworkSession::stop() +{ + if (d) + d->stop(); +} + +/*! + Returns the QNetworkConfiguration that this network session object is based on. + + \sa QNetworkConfiguration +*/ +QNetworkConfiguration QNetworkSession::configuration() const +{ + return d ? d->publicConfig : QNetworkConfiguration(); +} + +#ifndef QT_NO_NETWORKINTERFACE +/*! + Returns the network interface that is used by this session. + + This function only returns a valid QNetworkInterface when this session is \l Connected. + + The returned interface may change as a result of a roaming process. + + Note: this function does not work in Symbian emulator due to the way the + connectivity is emulated on Windows. + + \sa state() +*/ +QNetworkInterface QNetworkSession::interface() const +{ + return d ? d->currentInterface() : QNetworkInterface(); +} +#endif + +/*! + Returns true if this session is open. If the number of all open sessions is greater than + zero the underlying network interface will remain connected/up. + + The session can be controlled via open() and close(). +*/ +bool QNetworkSession::isOpen() const +{ + return d ? d->isOpen : false; +} + +/*! + Returns the state of the session. + + If the session is based on a single access point configuration the state of the + session is the same as the state of the associated network interface. Therefore + a network session object can be used to monitor network interfaces. + + A \l QNetworkConfiguration::ServiceNetwork based session summarizes the state of all its children + and therefore returns the \l Connected state if at least one of the service network's + \l {QNetworkConfiguration::children()}{children()} configurations is active. + + Note that it is not required to hold an open session in order to obtain the network interface state. + A connected but closed session may be used to monitor network interfaces whereas an open and connected + session object may prevent the network interface from being shut down. + + \sa error(), stateChanged() +*/ +QNetworkSession::State QNetworkSession::state() const +{ + return d ? d->state : QNetworkSession::Invalid; +} + +/*! + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ +QNetworkSession::SessionError QNetworkSession::error() const +{ + return d ? d->error() : InvalidConfigurationError; +} + +/*! + Returns a human-readable description of the last device error that + occurred. + + \sa error() +*/ +QString QNetworkSession::errorString() const +{ + return d ? d->errorString() : tr("Invalid configuration."); +} + +/*! + Returns the value for property \a key. + + A network session can have properties attached which may describe the session in more details. + This function can be used to gain access to those properties. + + The following property keys are guaranteed to be specified on all platforms: + + \table + \header + \o Key \o Description + \row + \o ActiveConfiguration + \o If the session \l isOpen() this property returns the identifier of the + QNetworkConfiguration that is used by this session; otherwise an empty string. + + The main purpose of this key is to determine which Internet access point is used + if the session is based on a \l{QNetworkConfiguration::ServiceNetwork}{ServiceNetwork}. + The following code snippet highlights the difference: + \code + QNetworkConfigurationManager mgr; + QNetworkConfiguration ap = mgr.defaultConfiguration(); + QNetworkSession *session = new QNetworkSession(ap); + ... //code activates session + + QString ident = session->sessionProperty("ActiveConfiguration").toString(); + if ( ap.type() == QNetworkConfiguration::ServiceNetwork ) { + Q_ASSERT( ap.identifier() != ident ); + Q_ASSERT( ap.children().contains( mgr.configurationFromIdentifier(ident) ) ); + } else if ( ap.type() == QNetworkConfiguration::InternetAccessPoint ) { + Q_ASSERT( ap.identifier() == ident ); + } + \endcode + \row + \o UserChoiceConfiguration + \o If the session \l isOpen() and is bound to a QNetworkConfiguration of type + UserChoice, this property returns the identifier of the QNetworkConfiguration that the + configuration resolved to when \l open() was called; otherwise an empty string. + + The purpose of this key is to determine the real QNetworkConfiguration that the + session is using. This key is different from \e ActiveConfiguration in that + this key may return an identifier for either a + \l {QNetworkConfiguration::ServiceNetwork}{service network} or a + \l {QNetworkConfiguration::InternetAccessPoint}{Internet access points} configurations, + whereas \e ActiveConfiguration always returns identifiers to + \l {QNetworkConfiguration::InternetAccessPoint}{Internet access points} configurations. + \row + \o ConnectInBackground + \o Setting this property to \e true before calling \l open() implies that the connection attempt + is made but if no connection can be established, the user is not connsulted and asked to select + a suitable connection. This property is not set by default and support for it depends on the platform. + + \row + \o AutoCloseSessionTimeout + \o If the session requires polling to keep its state up to date, this property holds + the timeout in milliseconds before the session will automatically close. If the + value of this property is -1 the session will not automatically close. This property + is set to -1 by default. + + The purpose of this property is to minimize resource use on platforms that use + polling to update the state of the session. Applications can set the value of this + property to the desired timeout before the session is closed. In response to the + closed() signal the network session should be deleted to ensure that all polling is + stopped. The session can then be recreated once it is required again. This property + has no effect for sessions that do not require polling. + \endtable +*/ +QVariant QNetworkSession::sessionProperty(const QString &key) const +{ + if (!d || !d->publicConfig.isValid()) + return QVariant(); + + if (key == QLatin1String("ActiveConfiguration")) + return d->isOpen ? d->activeConfig.identifier() : QString(); + + if (key == QLatin1String("UserChoiceConfiguration")) { + if (!d->isOpen || d->publicConfig.type() != QNetworkConfiguration::UserChoice) + return QString(); + + if (d->serviceConfig.isValid()) + return d->serviceConfig.identifier(); + else + return d->activeConfig.identifier(); + } + + return d->sessionProperty(key); +} + +/*! + Sets the property \a value on the session. The property is identified using + \a key. Removing an already set property can be achieved by passing an + invalid QVariant. + + Note that the \e UserChoiceConfiguration and \e ActiveConfiguration + properties are read only and cannot be changed using this method. +*/ +void QNetworkSession::setSessionProperty(const QString &key, const QVariant &value) +{ + if (!d) + return; + + if (key == QLatin1String("ActiveConfiguration") || + key == QLatin1String("UserChoiceConfiguration")) { + return; + } + + d->setSessionProperty(key, value); +} + +/*! + Instructs the session to roam to the new access point. The old access point remains active + until the application calls accept(). + + The newConfigurationActivated() signal is emitted once roaming has been completed. + + \sa accept() +*/ +void QNetworkSession::migrate() +{ + if (d) + d->migrate(); +} + +/*! + This function indicates that the application does not wish to roam the session. + + \sa migrate() +*/ +void QNetworkSession::ignore() +{ + // Needed on at least Symbian platform: the roaming must be explicitly + // ignore()'d or migrate()'d + if (d) + d->ignore(); +} + +/*! + Instructs the session to permanently accept the new access point. Once this function + has been called the session may not return to the old access point. + + The old access point may be closed in the process if there are no other network sessions for it. + Therefore any open socket that still uses the old access point + may become unusable and should be closed before completing the migration. +*/ +void QNetworkSession::accept() +{ + if (d) + d->accept(); +} + +/*! + The new access point is not suitable for the application. By calling this function the + session returns to the previous access point/configuration. This action may invalidate + any socket that has been created via the not desired access point. + + \sa accept() +*/ +void QNetworkSession::reject() +{ + if (d) + d->reject(); +} + + +/*! + Returns the amount of data sent in bytes; otherwise 0. + + This field value includes the usage across all open network + sessions which use the same network interface. + + If the session is based on a service network configuration the number of + sent bytes across all active member configurations are returned. + + This function may not always be supported on all platforms and returns 0. + The platform capability can be detected via QNetworkConfigurationManager::DataStatistics. + + \note On some platforms this function may run the main event loop. +*/ +quint64 QNetworkSession::bytesWritten() const +{ + return d ? d->bytesWritten() : Q_UINT64_C(0); +} + +/*! + Returns the amount of data received in bytes; otherwise 0. + + This field value includes the usage across all open network + sessions which use the same network interface. + + If the session is based on a service network configuration the number of + sent bytes across all active member configurations are returned. + + This function may not always be supported on all platforms and returns 0. + The platform capability can be detected via QNetworkConfigurationManager::DataStatistics. + + \note On some platforms this function may run the main event loop. +*/ +quint64 QNetworkSession::bytesReceived() const +{ + return d ? d->bytesReceived() : Q_UINT64_C(0); +} + +/*! + Returns the number of seconds that the session has been active. +*/ +quint64 QNetworkSession::activeTime() const +{ + return d ? d->activeTime() : Q_UINT64_C(0); +} + +/*! + \internal + + This function is required to detect whether the client wants to control + the roaming process. If he connects to preferredConfigurationChanged() signal + he intends to influence it. Otherwise QNetworkSession always roams + without registering this session as a stakeholder in the roaming process. + + For more details check the Forced vs ALR roaming section in the QNetworkSession + class description. +*/ +void QNetworkSession::connectNotify(const char *signal) +{ + QObject::connectNotify(signal); + + if (!d) + return; + + //check for preferredConfigurationChanged() signal connect notification + //This is not required on all platforms + if (qstrcmp(signal, SIGNAL(preferredConfigurationChanged(QNetworkConfiguration,bool))) == 0) + d->setALREnabled(true); +} + +/*! + \internal + + This function is called when the client disconnects from the + preferredConfigurationChanged() signal. + + \sa connectNotify() +*/ +void QNetworkSession::disconnectNotify(const char *signal) +{ + QObject::disconnectNotify(signal); + + if (!d) + return; + + //check for preferredConfigurationChanged() signal disconnect notification + //This is not required on all platforms + if (qstrcmp(signal, SIGNAL(preferredConfigurationChanged(QNetworkConfiguration,bool))) == 0) + d->setALREnabled(false); +} + +#ifdef Q_OS_SYMBIAN +RConnection* QNetworkSessionPrivate::nativeSession(QNetworkSession &s) +{ + if (!s.d) + return 0; + if (s.thread() != QThread::currentThread()) + qWarning("QNetworkSessionPrivate::nativeSession called in wrong thread"); + return s.d->nativeSession(); +} + +TInt QNetworkSessionPrivate::nativeOpenSocket(QNetworkSession& s, RSocket& sock, TUint family, TUint type, TUint protocol) +{ + if (!s.d) + return 0; + QMutexLocker lock(&(s.d->mutex)); + RConnection *con = s.d->nativeSession(); + if (!con || !con->SubSessionHandle()) + return KErrNotReady; + return sock.Open(qt_symbianGetSocketServer(), family, type, protocol, *con); +} + +TInt QNetworkSessionPrivate::nativeOpenHostResolver(QNetworkSession& s, RHostResolver& resolver, TUint family, TUint protocol) +{ + if (!s.d) + return 0; + QMutexLocker lock(&(s.d->mutex)); + RConnection *con = s.d->nativeSession(); + if (!con || !con->SubSessionHandle()) + return KErrNotReady; + return resolver.Open(qt_symbianGetSocketServer(), family, protocol, *con); +} + +#endif + +#include "moc_qnetworksession.cpp" + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT diff --git a/src/network/bearer/qnetworksession.h b/src/network/bearer/qnetworksession.h new file mode 100644 index 0000000000..688f37eba4 --- /dev/null +++ b/src/network/bearer/qnetworksession.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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 QNETWORKSESSION_H +#define QNETWORKSESSION_H + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> +#include <QtNetwork/qnetworkinterface.h> +#include <QtCore/qvariant.h> +#include <QtNetwork/qnetworkconfiguration.h> + +#ifndef QT_NO_BEARERMANAGEMENT + +#if defined(Q_OS_WIN) && defined(interface) +#undef interface +#endif + +QT_BEGIN_HEADER + +#ifndef QT_MOBILITY_BEARER +#include <QtCore/qshareddata.h> +QT_BEGIN_NAMESPACE +QT_MODULE(Network) +#define QNetworkSessionExport Q_NETWORK_EXPORT +#else +#include "qmobilityglobal.h" +QTM_BEGIN_NAMESPACE +#define QNetworkSessionExport Q_BEARER_EXPORT +#endif + +class QNetworkSessionPrivate; +class QNetworkSessionExport QNetworkSession : public QObject +{ + Q_OBJECT + +public: + enum State { + Invalid = 0, + NotAvailable, + Connecting, + Connected, + Closing, + Disconnected, + Roaming + }; + + enum SessionError { + UnknownSessionError = 0, + SessionAbortedError, + RoamingError, + OperationNotSupportedError, + InvalidConfigurationError + }; + + explicit QNetworkSession(const QNetworkConfiguration &connConfig, QObject *parent = 0); + virtual ~QNetworkSession(); + + bool isOpen() const; + QNetworkConfiguration configuration() const; +#ifndef QT_NO_NETWORKINTERFACE + QNetworkInterface interface() const; +#endif + + State state() const; + SessionError error() const; + QString errorString() const; + QVariant sessionProperty(const QString &key) const; + void setSessionProperty(const QString &key, const QVariant &value); + + quint64 bytesWritten() const; + quint64 bytesReceived() const; + quint64 activeTime() const; + + bool waitForOpened(int msecs = 30000); + +public Q_SLOTS: + void open(); + void close(); + void stop(); + + //roaming related slots + void migrate(); + void ignore(); + void accept(); + void reject(); + +Q_SIGNALS: + void stateChanged(QNetworkSession::State); + void opened(); + void closed(); + void error(QNetworkSession::SessionError); + void preferredConfigurationChanged(const QNetworkConfiguration &config, bool isSeamless); + void newConfigurationActivated(); + +protected: + virtual void connectNotify(const char *signal); + virtual void disconnectNotify(const char *signal); + +private: + friend class QNetworkSessionPrivate; + QNetworkSessionPrivate *d; +}; + +#ifndef QT_MOBILITY_BEARER +QT_END_NAMESPACE +Q_DECLARE_METATYPE(QNetworkSession::State) +Q_DECLARE_METATYPE(QNetworkSession::SessionError) +#else +QTM_END_NAMESPACE +#endif + +QT_END_HEADER + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QNETWORKSESSION_H diff --git a/src/network/bearer/qnetworksession_p.h b/src/network/bearer/qnetworksession_p.h new file mode 100644 index 0000000000..a92b7ce315 --- /dev/null +++ b/src/network/bearer/qnetworksession_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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 QNETWORKSESSIONPRIVATE_H +#define QNETWORKSESSIONPRIVATE_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 "qnetworksession.h" +#include "qnetworkconfiguration_p.h" +#include "QtCore/qsharedpointer.h" + +#ifndef QT_NO_BEARERMANAGEMENT + +#ifdef Q_OS_SYMBIAN +class RConnection; +class RSocket; +class RHostResolver; +#endif + +QT_BEGIN_NAMESPACE + +class Q_NETWORK_EXPORT QNetworkSessionPrivate : public QObject +{ + Q_OBJECT + + friend class QNetworkSession; + +public: + QNetworkSessionPrivate() : QObject(), + state(QNetworkSession::Invalid), isOpen(false), mutex(QMutex::Recursive) + {} + virtual ~QNetworkSessionPrivate() + {} + + //called by QNetworkSession constructor and ensures + //that the state is immediately updated (w/o actually opening + //a session). Also this function should take care of + //notification hooks to discover future state changes. + virtual void syncStateWithInterface() = 0; + +#ifndef QT_NO_NETWORKINTERFACE + virtual QNetworkInterface currentInterface() const = 0; +#endif + virtual QVariant sessionProperty(const QString &key) const = 0; + virtual void setSessionProperty(const QString &key, const QVariant &value) = 0; + + virtual void open() = 0; + virtual void close() = 0; + virtual void stop() = 0; + + virtual void setALREnabled(bool /*enabled*/) {} + virtual void migrate() = 0; + virtual void accept() = 0; + virtual void ignore() = 0; + virtual void reject() = 0; + + virtual QString errorString() const = 0; //must return translated string + virtual QNetworkSession::SessionError error() const = 0; + + virtual quint64 bytesWritten() const = 0; + virtual quint64 bytesReceived() const = 0; + virtual quint64 activeTime() const = 0; + +#ifdef Q_OS_SYMBIAN + // get internal RConnection (not thread safe, call only from thread that owns the QNetworkSession) + static RConnection* nativeSession(QNetworkSession&); + virtual RConnection* nativeSession() = 0; + // open socket using the internal RConnection (thread safe) + static TInt nativeOpenSocket(QNetworkSession& session, RSocket& socket, TUint family, TUint type, TUint protocol); + // open host resolver using the internal RConnection (thread safe) + static TInt nativeOpenHostResolver(QNetworkSession& session, RHostResolver& resolver, TUint family, TUint protocol); +#endif +protected: + inline QNetworkConfigurationPrivatePointer privateConfiguration(const QNetworkConfiguration &config) const + { + return config.d; + } + + inline void setPrivateConfiguration(QNetworkConfiguration &config, + QNetworkConfigurationPrivatePointer ptr) const + { + config.d = ptr; + } + +Q_SIGNALS: + //releases any pending waitForOpened() calls + void quitPendingWaitsForOpened(); + + void error(QNetworkSession::SessionError error); + void stateChanged(QNetworkSession::State state); + void closed(); + void newConfigurationActivated(); + void preferredConfigurationChanged(const QNetworkConfiguration &config, bool isSeamless); + +protected: + QNetworkSession *q; + + // The config set on QNetworkSession. + QNetworkConfiguration publicConfig; + + // If publicConfig is a ServiceNetwork this is a copy of publicConfig. + // If publicConfig is an UserChoice that is resolved to a ServiceNetwork this is the actual + // ServiceNetwork configuration. + QNetworkConfiguration serviceConfig; + + // This is the actual active configuration currently in use by the session. + // Either a copy of publicConfig or one of serviceConfig.children(). + QNetworkConfiguration activeConfig; + + QNetworkSession::State state; + bool isOpen; + + QMutex mutex; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSharedPointer<QNetworkSession>) + +#endif // QT_NO_BEARERMANAGEMENT + +#endif // QNETWORKSESSIONPRIVATE_H diff --git a/src/network/bearer/qsharednetworksession.cpp b/src/network/bearer/qsharednetworksession.cpp new file mode 100644 index 0000000000..fcb0128817 --- /dev/null +++ b/src/network/bearer/qsharednetworksession.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "qsharednetworksession_p.h" +#include "qbearerengine_p.h" +#include <QThreadStorage> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +QThreadStorage<QSharedNetworkSessionManager *> tls; + +inline QSharedNetworkSessionManager* sharedNetworkSessionManager() +{ + QSharedNetworkSessionManager* rv = tls.localData(); + if (!rv) { + rv = new QSharedNetworkSessionManager; + tls.setLocalData(rv); + } + return rv; +} + +static void doDeleteLater(QObject* obj) +{ + obj->deleteLater(); +} + +QSharedPointer<QNetworkSession> QSharedNetworkSessionManager::getSession(QNetworkConfiguration config) +{ + QSharedNetworkSessionManager *m(sharedNetworkSessionManager()); + //if already have a session, return it + if (m->sessions.contains(config)) { + QSharedPointer<QNetworkSession> p = m->sessions.value(config).toStrongRef(); + if (!p.isNull()) + return p; + } + //otherwise make one + QSharedPointer<QNetworkSession> session(new QNetworkSession(config), doDeleteLater); + m->sessions[config] = session; + return session; +} + +void QSharedNetworkSessionManager::setSession(QNetworkConfiguration config, QSharedPointer<QNetworkSession> session) +{ + QSharedNetworkSessionManager *m(sharedNetworkSessionManager()); + m->sessions[config] = session; +} + +uint qHash(const QNetworkConfiguration& config) +{ + return ((uint)config.type()) | (((uint)config.bearerType()) << 8) | (((uint)config.purpose()) << 16); +} + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT diff --git a/src/network/bearer/qsharednetworksession_p.h b/src/network/bearer/qsharednetworksession_p.h new file mode 100644 index 0000000000..25b4ec2357 --- /dev/null +++ b/src/network/bearer/qsharednetworksession_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QSHAREDNETWORKSESSIONPRIVATE_H +#define QSHAREDNETWORKSESSIONPRIVATE_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 "qnetworksession.h" +#include "qnetworkconfiguration.h" +#include <QHash> +#include <QSharedPointer> +#include <QWeakPointer> +#include <QMutex> + +#ifndef QT_NO_BEARERMANAGEMENT + +QT_BEGIN_NAMESPACE + +uint qHash(const QNetworkConfiguration& config); + +class QSharedNetworkSessionManager +{ +public: + static QSharedPointer<QNetworkSession> getSession(QNetworkConfiguration config); + static void setSession(QNetworkConfiguration config, QSharedPointer<QNetworkSession> session); +private: + QHash<QNetworkConfiguration, QWeakPointer<QNetworkSession> > sessions; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_BEARERMANAGEMENT + +#endif //QSHAREDNETWORKSESSIONPRIVATE_H + diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri new file mode 100644 index 0000000000..bb98305173 --- /dev/null +++ b/src/network/kernel/kernel.pri @@ -0,0 +1,34 @@ +# Qt network kernel module + +PRECOMPILED_HEADER = ../corelib/global/qt_pch.h +INCLUDEPATH += $$PWD + +HEADERS += kernel/qauthenticator.h \ + kernel/qauthenticator_p.h \ + kernel/qhostaddress.h \ + kernel/qhostinfo.h \ + kernel/qhostinfo_p.h \ + kernel/qurlinfo.h \ + kernel/qnetworkproxy.h \ + kernel/qnetworkinterface.h \ + kernel/qnetworkinterface_p.h + +SOURCES += kernel/qauthenticator.cpp \ + kernel/qhostaddress.cpp \ + kernel/qhostinfo.cpp \ + kernel/qurlinfo.cpp \ + kernel/qnetworkproxy.cpp \ + kernel/qnetworkinterface.cpp + +symbian: SOURCES += kernel/qhostinfo_symbian.cpp kernel/qnetworkinterface_symbian.cpp +unix:!symbian:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp +win32:SOURCES += kernel/qhostinfo_win.cpp kernel/qnetworkinterface_win.cpp +integrity:SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp + +mac:LIBS_PRIVATE += -framework SystemConfiguration -framework CoreFoundation +mac:SOURCES += kernel/qnetworkproxy_mac.cpp +else:win32:SOURCES += kernel/qnetworkproxy_win.cpp +else:symbian:SOURCES += kernel/qnetworkproxy_symbian.cpp +else:SOURCES += kernel/qnetworkproxy_generic.cpp + +symbian: LIBS += -lcommsdat diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp new file mode 100644 index 0000000000..d61d3b7fa9 --- /dev/null +++ b/src/network/kernel/qauthenticator.cpp @@ -0,0 +1,1428 @@ +/**************************************************************************** +** +** 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 <qauthenticator.h> +#include <qauthenticator_p.h> +#include <qdebug.h> +#include <qhash.h> +#include <qbytearray.h> +#include <qcryptographichash.h> +#include <qhttp.h> +#include <qiodevice.h> +#include <qdatastream.h> +#include <qendian.h> +#include <qstring.h> +#include <qdatetime.h> + +//#define NTLMV1_CLIENT + +QT_BEGIN_NAMESPACE + +#ifdef NTLMV1_CLIENT +#include "../../3rdparty/des/des.cpp" +#endif + +static QByteArray qNtlmPhase1(); +static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data); + +/*! + \class QAuthenticator + \brief The QAuthenticator class provides an authentication object. + \since 4.3 + + \reentrant + \ingroup network + \inmodule QtNetwork + + The QAuthenticator class is usually used in the + \l{QNetworkAccessManager::}{authenticationRequired()} and + \l{QNetworkAccessManager::}{proxyAuthenticationRequired()} signals of QNetworkAccessManager and + QAbstractSocket. The class provides a way to pass back the required + authentication information to the socket when accessing services that + require authentication. + + QAuthenticator supports the following authentication methods: + \list + \o Basic + \o NTLM version 1 + \o Digest-MD5 + \endlist + + Note that, in particular, NTLM version 2 is not supported. + + \section1 Options + + In addition to the username and password required for authentication, a + QAuthenticator object can also contain additional options. The + options() function can be used to query incoming options sent by + the server; the setOption() function can + be used to set outgoing options, to be processed by the authenticator + calculation. The options accepted and provided depend on the authentication + type (see method()). + + The following tables list known incoming options as well as accepted + outgoing options. The list of incoming options is not exhaustive, since + servers may include additional information at any time. The list of + outgoing options is exhaustive, however, and no unknown options will be + treated or sent back to the server. + + \section2 Basic + + \table + \header \o Option \o Direction \o Description + \row \o \tt{realm} \o Incoming \o Contains the realm of the authentication, the same as realm() + \endtable + + The Basic authentication mechanism supports no outgoing options. + + \section2 NTLM version 1 + + The NTLM authentication mechanism currently supports no incoming or outgoing options. + + \section2 Digest-MD5 + + \table + \header \o Option \o Direction \o Description + \row \o \tt{realm} \o Incoming \o Contains the realm of the authentication, the same as realm() + \endtable + + The Digest-MD5 authentication mechanism supports no outgoing options. + + \sa QSslSocket +*/ + + +/*! + Constructs an empty authentication object +*/ +QAuthenticator::QAuthenticator() + : d(0) +{ +} + +/*! + Destructs the object +*/ +QAuthenticator::~QAuthenticator() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Constructs a copy of \a other. +*/ +QAuthenticator::QAuthenticator(const QAuthenticator &other) + : d(other.d) +{ + if (d) + d->ref.ref(); +} + +/*! + Assigns the contents of \a other to this authenticator. +*/ +QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other) +{ + if (d == other.d) + return *this; + + if (d && !d->ref.deref()) + delete d; + + d = other.d; + if (d) + d->ref.ref(); + return *this; +} + +/*! + Returns true if this authenticator is identical to \a other; otherwise + returns false. +*/ +bool QAuthenticator::operator==(const QAuthenticator &other) const +{ + if (d == other.d) + return true; + return d->user == other.d->user + && d->password == other.d->password + && d->realm == other.d->realm + && d->method == other.d->method + && d->options == other.d->options; +} + +/*! + \fn bool QAuthenticator::operator!=(const QAuthenticator &other) const + + Returns true if this authenticator is different from \a other; otherwise + returns false. +*/ + +/*! + returns the user used for authentication. +*/ +QString QAuthenticator::user() const +{ + return d ? d->user : QString(); +} + +/*! + Sets the \a user used for authentication. +*/ +void QAuthenticator::setUser(const QString &user) +{ + detach(); + int separatorPosn = 0; + + switch(d->method) { + case QAuthenticatorPrivate::Ntlm: + if((separatorPosn = user.indexOf(QLatin1String("\\"))) != -1) { + //domain name is present + d->realm.clear(); + d->userDomain = user.left(separatorPosn); + d->extractedUser = user.mid(separatorPosn + 1); + d->user = user; + } else if((separatorPosn = user.indexOf(QLatin1String("@"))) != -1) { + //domain name is present + d->realm.clear(); + d->userDomain = user.left(separatorPosn); + d->extractedUser = user.left(separatorPosn); + d->user = user; + } else { + d->extractedUser = user; + d->user = user; + d->realm.clear(); + d->userDomain.clear(); + } + break; + default: + d->user = user; + d->userDomain.clear(); + break; + } +} + +/*! + returns the password used for authentication. +*/ +QString QAuthenticator::password() const +{ + return d ? d->password : QString(); +} + +/*! + Sets the \a password used for authentication. +*/ +void QAuthenticator::setPassword(const QString &password) +{ + detach(); + d->password = password; +} + +/*! + \internal +*/ +void QAuthenticator::detach() +{ + if (!d) { + d = new QAuthenticatorPrivate; + d->ref = 1; + return; + } + + qAtomicDetach(d); + d->phase = QAuthenticatorPrivate::Start; +} + +/*! + returns the realm requiring authentication. +*/ +QString QAuthenticator::realm() const +{ + return d ? d->realm : QString(); +} + +/*! + \since 4.7 + Returns the value related to option \a opt if it was set by the server. + See \l{QAuthenticator#Options} for more information on incoming options. + If option \a opt isn't found, an invalid QVariant will be returned. + + \sa options(), QAuthenticator#Options +*/ +QVariant QAuthenticator::option(const QString &opt) const +{ + return d ? d->options.value(opt) : QVariant(); +} + +/*! + \since 4.7 + Returns all incoming options set in this QAuthenticator object by parsing + the server reply. See \l{QAuthenticator#Options} for more information + on incoming options. + + \sa option(), QAuthenticator#Options +*/ +QVariantHash QAuthenticator::options() const +{ + return d ? d->options : QVariantHash(); +} + +/*! + \since 4.7 + + Sets the outgoing option \a opt to value \a value. + See \l{QAuthenticator#Options} for more information on outgoing options. + + \sa options(), option(), QAuthenticator#Options +*/ +void QAuthenticator::setOption(const QString &opt, const QVariant &value) +{ + detach(); + d->options.insert(opt, value); +} + + +/*! + Returns true if the authenticator is null. +*/ +bool QAuthenticator::isNull() const +{ + return !d; +} + +QAuthenticatorPrivate::QAuthenticatorPrivate() + : ref(0) + , method(None) + , phase(Start) + , nonceCount(0) +{ + cnonce = QCryptographicHash::hash(QByteArray::number(qrand(), 16) + QByteArray::number(qrand(), 16), + QCryptographicHash::Md5).toHex(); + nonceCount = 0; +} + +#ifndef QT_NO_HTTP +void QAuthenticatorPrivate::parseHttpResponse(const QHttpResponseHeader &header, bool isProxy) +{ + const QList<QPair<QString, QString> > values = header.values(); + QList<QPair<QByteArray, QByteArray> > rawValues; + + QList<QPair<QString, QString> >::const_iterator it, end; + for (it = values.constBegin(), end = values.constEnd(); it != end; ++it) + rawValues.append(qMakePair(it->first.toLatin1(), it->second.toUtf8())); + + // continue in byte array form + parseHttpResponse(rawValues, isProxy); +} +#endif + +void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray> > &values, bool isProxy) +{ + const char *search = isProxy ? "proxy-authenticate" : "www-authenticate"; + + method = None; + /* + Fun from the HTTP 1.1 specs, that we currently ignore: + + User agents are advised to take special care in parsing the WWW- + Authenticate field value as it might contain more than one challenge, + or if more than one WWW-Authenticate header field is provided, the + contents of a challenge itself can contain a comma-separated list of + authentication parameters. + */ + + QByteArray headerVal; + for (int i = 0; i < values.size(); ++i) { + const QPair<QByteArray, QByteArray> ¤t = values.at(i); + if (current.first.toLower() != search) + continue; + QByteArray str = current.second.toLower(); + if (method < Basic && str.startsWith("basic")) { + method = Basic; + headerVal = current.second.mid(6); + } else if (method < Ntlm && str.startsWith("ntlm")) { + method = Ntlm; + headerVal = current.second.mid(5); + } else if (method < DigestMd5 && str.startsWith("digest")) { + method = DigestMd5; + headerVal = current.second.mid(7); + } + } + + challenge = headerVal.trimmed(); + QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge); + + switch(method) { + case Basic: + if(realm.isEmpty()) + this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm")); + if (user.isEmpty()) + phase = Done; + break; + case Ntlm: + // #### extract from header + break; + case DigestMd5: { + if(realm.isEmpty()) + this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm")); + if (options.value("stale").toLower() == "true") + phase = Start; + if (user.isEmpty()) + phase = Done; + break; + } + default: + realm.clear(); + challenge = QByteArray(); + phase = Invalid; + } +} + +QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path) +{ + QByteArray response; + const char *methodString = 0; + switch(method) { + case QAuthenticatorPrivate::None: + methodString = ""; + phase = Done; + break; + case QAuthenticatorPrivate::Plain: + response = '\0' + user.toUtf8() + '\0' + password.toUtf8(); + phase = Done; + break; + case QAuthenticatorPrivate::Basic: + methodString = "Basic "; + response = user.toLatin1() + ':' + password.toLatin1(); + response = response.toBase64(); + phase = Done; + break; + case QAuthenticatorPrivate::Login: + if (challenge.contains("VXNlciBOYW1lAA==")) { + response = user.toUtf8().toBase64(); + phase = Phase2; + } else if (challenge.contains("UGFzc3dvcmQA")) { + response = password.toUtf8().toBase64(); + phase = Done; + } + break; + case QAuthenticatorPrivate::CramMd5: + break; + case QAuthenticatorPrivate::DigestMd5: + methodString = "Digest "; + response = digestMd5Response(challenge, requestMethod, path); + phase = Done; + break; + case QAuthenticatorPrivate::Ntlm: + methodString = "NTLM "; + if (challenge.isEmpty()) { + response = qNtlmPhase1().toBase64(); + if (user.isEmpty()) + phase = Done; + else + phase = Phase2; + } else { + response = qNtlmPhase3(this, QByteArray::fromBase64(challenge)).toBase64(); + phase = Done; + } + + break; + } + return QByteArray(methodString) + response; +} + + +// ---------------------------- Digest Md5 code ---------------------------------------- + +QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationChallenge(const QByteArray &challenge) +{ + QHash<QByteArray, QByteArray> options; + // parse the challenge + const char *d = challenge.constData(); + const char *end = d + challenge.length(); + while (d < end) { + while (d < end && (*d == ' ' || *d == '\n' || *d == '\r')) + ++d; + const char *start = d; + while (d < end && *d != '=') + ++d; + QByteArray key = QByteArray(start, d - start); + ++d; + if (d >= end) + break; + bool quote = (*d == '"'); + if (quote) + ++d; + if (d >= end) + break; + start = d; + QByteArray value; + while (d < end) { + bool backslash = false; + if (*d == '\\' && d < end - 1) { + ++d; + backslash = true; + } + if (!backslash) { + if (quote) { + if (*d == '"') + break; + } else { + if (*d == ',') + break; + } + } + value += *d; + ++d; + } + while (d < end && *d != ',') + ++d; + ++d; + options[key] = value; + } + + QByteArray qop = options.value("qop"); + if (!qop.isEmpty()) { + QList<QByteArray> qopoptions = qop.split(','); + if (!qopoptions.contains("auth")) + return QHash<QByteArray, QByteArray>(); + // #### can't do auth-int currently +// if (qop.contains("auth-int")) +// qop = "auth-int"; +// else if (qop.contains("auth")) +// qop = "auth"; +// else +// qop = QByteArray(); + options["qop"] = "auth"; + } + + return options; +} + +/* + Digest MD5 implementation + + Code taken from RFC 2617 + + Currently we don't support the full SASL authentication mechanism (which includes cyphers) +*/ + + +/* calculate request-digest/response-digest as per HTTP Digest spec */ +static QByteArray digestMd5ResponseHelper( + const QByteArray &alg, + const QByteArray &userName, + const QByteArray &realm, + const QByteArray &password, + const QByteArray &nonce, /* nonce from server */ + const QByteArray &nonceCount, /* 8 hex digits */ + const QByteArray &cNonce, /* client nonce */ + const QByteArray &qop, /* qop-value: "", "auth", "auth-int" */ + const QByteArray &method, /* method from the request */ + const QByteArray &digestUri, /* requested URL */ + const QByteArray &hEntity /* H(entity body) if qop="auth-int" */ + ) +{ + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(userName); + hash.addData(":", 1); + hash.addData(realm); + hash.addData(":", 1); + hash.addData(password); + QByteArray ha1 = hash.result(); + if (alg.toLower() == "md5-sess") { + hash.reset(); + // RFC 2617 contains an error, it was: + // hash.addData(ha1); + // but according to the errata page at http://www.rfc-editor.org/errata_list.php, ID 1649, it + // must be the following line: + hash.addData(ha1.toHex()); + hash.addData(":", 1); + hash.addData(nonce); + hash.addData(":", 1); + hash.addData(cNonce); + ha1 = hash.result(); + }; + ha1 = ha1.toHex(); + + // calculate H(A2) + hash.reset(); + hash.addData(method); + hash.addData(":", 1); + hash.addData(digestUri); + if (qop.toLower() == "auth-int") { + hash.addData(":", 1); + hash.addData(hEntity); + } + QByteArray ha2hex = hash.result().toHex(); + + // calculate response + hash.reset(); + hash.addData(ha1); + hash.addData(":", 1); + hash.addData(nonce); + hash.addData(":", 1); + if (!qop.isNull()) { + hash.addData(nonceCount); + hash.addData(":", 1); + hash.addData(cNonce); + hash.addData(":", 1); + hash.addData(qop); + hash.addData(":", 1); + } + hash.addData(ha2hex); + return hash.result().toHex(); +} + +QByteArray QAuthenticatorPrivate::digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path) +{ + QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge); + + ++nonceCount; + QByteArray nonceCountString = QByteArray::number(nonceCount, 16); + while (nonceCountString.length() < 8) + nonceCountString.prepend('0'); + + QByteArray nonce = options.value("nonce"); + QByteArray opaque = options.value("opaque"); + QByteArray qop = options.value("qop"); + +// qDebug() << "calculating digest: method=" << method << "path=" << path; + QByteArray response = digestMd5ResponseHelper(options.value("algorithm"), user.toLatin1(), + realm.toLatin1(), password.toLatin1(), + nonce, nonceCountString, + cnonce, qop, method, + path, QByteArray()); + + + QByteArray credentials; + credentials += "username=\"" + user.toLatin1() + "\", "; + credentials += "realm=\"" + realm.toLatin1() + "\", "; + credentials += "nonce=\"" + nonce + "\", "; + credentials += "uri=\"" + path + "\", "; + if (!opaque.isEmpty()) + credentials += "opaque=\"" + opaque + "\", "; + credentials += "response=\"" + response + '\"'; + if (!options.value("algorithm").isEmpty()) + credentials += ", algorithm=" + options.value("algorithm"); + if (!options.value("qop").isEmpty()) { + credentials += ", qop=" + qop + ", "; + credentials += "nc=" + nonceCountString + ", "; + credentials += "cnonce=\"" + cnonce + '\"'; + } + + return credentials; +} + +// ---------------------------- Digest Md5 code ---------------------------------------- + + + +/* + * NTLM message flags. + * + * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru> + * + * This software is released under the MIT license. + */ + +/* + * Indicates that Unicode strings are supported for use in security + * buffer data. + */ +#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 + +/* + * Indicates that OEM strings are supported for use in security buffer data. + */ +#define NTLMSSP_NEGOTIATE_OEM 0x00000002 + +/* + * Requests that the server's authentication realm be included in the + * Type 2 message. + */ +#define NTLMSSP_REQUEST_TARGET 0x00000004 + +/* + * Specifies that authenticated communication between the client and server + * should carry a digital signature (message integrity). + */ +#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 + +/* + * Specifies that authenticated communication between the client and server + * should be encrypted (message confidentiality). + */ +#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 + +/* + * Indicates that datagram authentication is being used. + */ +#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040 + +/* + * Indicates that the LAN Manager session key should be + * used for signing and sealing authenticated communications. + */ +#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 + +/* + * Indicates that NTLM authentication is being used. + */ +#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 + +/* + * Sent by the client in the Type 1 message to indicate that the name of the + * domain in which the client workstation has membership is included in the + * message. This is used by the server to determine whether the client is + * eligible for local authentication. + */ +#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000 + +/* + * Sent by the client in the Type 1 message to indicate that the client + * workstation's name is included in the message. This is used by the server + * to determine whether the client is eligible for local authentication. + */ +#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000 + +/* + * Sent by the server to indicate that the server and client are on the same + * machine. Implies that the client may use the established local credentials + * for authentication instead of calculating a response to the challenge. + */ +#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000 + +/* + * Indicates that authenticated communication between the client and server + * should be signed with a "dummy" signature. + */ +#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 + +/* + * Sent by the server in the Type 2 message to indicate that the target + * authentication realm is a domain. + */ +#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000 + +/* + * Sent by the server in the Type 2 message to indicate that the target + * authentication realm is a server. + */ +#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000 + +/* + * Sent by the server in the Type 2 message to indicate that the target + * authentication realm is a share. Presumably, this is for share-level + * authentication. Usage is unclear. + */ +#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000 + +/* + * Indicates that the NTLM2 signing and sealing scheme should be used for + * protecting authenticated communications. Note that this refers to a + * particular session security scheme, and is not related to the use of + * NTLMv2 authentication. + */ +#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000 + +/* + * Sent by the server in the Type 2 message to indicate that it is including + * a Target Information block in the message. The Target Information block + * is used in the calculation of the NTLMv2 response. + */ +#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000 + +/* + * Indicates that 128-bit encryption is supported. + */ +#define NTLMSSP_NEGOTIATE_128 0x20000000 + +/* + * Indicates that the client will provide an encrypted master session key in + * the "Session Key" field of the Type 3 message. This is used in signing and + * sealing, and is RC4-encrypted using the previous session key as the + * encryption key. + */ +#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000 + +/* + * Indicates that 56-bit encryption is supported. + */ +#define NTLMSSP_NEGOTIATE_56 0x80000000 + +/* + * AvId values + */ +#define AVTIMESTAMP 7 + +//#define NTLMV1_CLIENT + + +//************************Global variables*************************** + +const int blockSize = 64; //As per RFC2104 Block-size is 512 bits +const int nDigestLen = 16; //Trunctaion Length of the Hmac-Md5 digest +const quint8 respversion = 1; +const quint8 hirespversion = 1; + +/* usage: + // fill up ctx with what we know. + QByteArray response = qNtlmPhase1(ctx); + // send response (b64 encoded??) + // get response from server (b64 decode?) + Phase2Block pb; + qNtlmDecodePhase2(response, pb); + response = qNtlmPhase3(ctx, pb); + // send response (b64 encoded??) +*/ + +/* + TODO: + - Fix unicode handling + - add v2 handling +*/ + +class QNtlmBuffer { +public: + QNtlmBuffer() : len(0), maxLen(0), offset(0) {} + quint16 len; + quint16 maxLen; + quint32 offset; + enum { Size = 8 }; +}; + +class QNtlmPhase1BlockBase +{ +public: + char magic[8]; + quint32 type; + quint32 flags; + QNtlmBuffer domain; + QNtlmBuffer workstation; + enum { Size = 32 }; +}; + +// ################# check paddings +class QNtlmPhase2BlockBase +{ +public: + char magic[8]; + quint32 type; + QNtlmBuffer targetName; + quint32 flags; + unsigned char challenge[8]; + quint32 context[2]; + QNtlmBuffer targetInfo; + enum { Size = 48 }; +}; + +class QNtlmPhase3BlockBase { +public: + char magic[8]; + quint32 type; + QNtlmBuffer lmResponse; + QNtlmBuffer ntlmResponse; + QNtlmBuffer domain; + QNtlmBuffer user; + QNtlmBuffer workstation; + QNtlmBuffer sessionKey; + quint32 flags; + enum { Size = 64 }; +}; + +static void qStreamNtlmBuffer(QDataStream& ds, const QByteArray& s) +{ + ds.writeRawData(s.constData(), s.size()); +} + + +static void qStreamNtlmString(QDataStream& ds, const QString& s, bool unicode) +{ + if (!unicode) { + qStreamNtlmBuffer(ds, s.toLatin1()); + return; + } + const ushort *d = s.utf16(); + for (int i = 0; i < s.length(); ++i) + ds << d[i]; +} + + + +static int qEncodeNtlmBuffer(QNtlmBuffer& buf, int offset, const QByteArray& s) +{ + buf.len = s.size(); + buf.maxLen = buf.len; + buf.offset = (offset + 1) & ~1; + return buf.offset + buf.len; +} + + +static int qEncodeNtlmString(QNtlmBuffer& buf, int offset, const QString& s, bool unicode) +{ + if (!unicode) + return qEncodeNtlmBuffer(buf, offset, s.toLatin1()); + buf.len = 2 * s.length(); + buf.maxLen = buf.len; + buf.offset = (offset + 1) & ~1; + return buf.offset + buf.len; +} + + +static QDataStream& operator<<(QDataStream& s, const QNtlmBuffer& b) +{ + s << b.len << b.maxLen << b.offset; + return s; +} + +static QDataStream& operator>>(QDataStream& s, QNtlmBuffer& b) +{ + s >> b.len >> b.maxLen >> b.offset; + return s; +} + + +class QNtlmPhase1Block : public QNtlmPhase1BlockBase +{ // request +public: + QNtlmPhase1Block() { + qstrncpy(magic, "NTLMSSP", 8); + type = 1; + flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET; + } + + // extracted + QString domainStr, workstationStr; +}; + + +class QNtlmPhase2Block : public QNtlmPhase2BlockBase +{ // challenge +public: + QNtlmPhase2Block() { + magic[0] = 0; + type = 0xffffffff; + } + + // extracted + QString targetNameStr, targetInfoStr; + QByteArray targetInfoBuff; +}; + + + +class QNtlmPhase3Block : public QNtlmPhase3BlockBase { // response +public: + QNtlmPhase3Block() { + qstrncpy(magic, "NTLMSSP", 8); + type = 3; + flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_TARGET_INFO; + } + + // extracted + QByteArray lmResponseBuf, ntlmResponseBuf; + QString domainStr, userStr, workstationStr, sessionKeyStr; + QByteArray v2Hash; +}; + + +static QDataStream& operator<<(QDataStream& s, const QNtlmPhase1Block& b) { + bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE); + + s.writeRawData(b.magic, sizeof(b.magic)); + s << b.type; + s << b.flags; + s << b.domain; + s << b.workstation; + if (!b.domainStr.isEmpty()) + qStreamNtlmString(s, b.domainStr, unicode); + if (!b.workstationStr.isEmpty()) + qStreamNtlmString(s, b.workstationStr, unicode); + return s; +} + + +static QDataStream& operator<<(QDataStream& s, const QNtlmPhase3Block& b) { + bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE); + s.writeRawData(b.magic, sizeof(b.magic)); + s << b.type; + s << b.lmResponse; + s << b.ntlmResponse; + s << b.domain; + s << b.user; + s << b.workstation; + s << b.sessionKey; + s << b.flags; + + if (!b.domainStr.isEmpty()) + qStreamNtlmString(s, b.domainStr, unicode); + + qStreamNtlmString(s, b.userStr, unicode); + + if (!b.workstationStr.isEmpty()) + qStreamNtlmString(s, b.workstationStr, unicode); + + // Send auth info + qStreamNtlmBuffer(s, b.lmResponseBuf); + qStreamNtlmBuffer(s, b.ntlmResponseBuf); + + + return s; +} + + +static QByteArray qNtlmPhase1() +{ + QByteArray rc; + QDataStream ds(&rc, QIODevice::WriteOnly); + ds.setByteOrder(QDataStream::LittleEndian); + QNtlmPhase1Block pb; + ds << pb; + return rc; +} + + +static QByteArray qStringAsUcs2Le(const QString& src) +{ + QByteArray rc(2*src.length(), 0); + const unsigned short *s = src.utf16(); + unsigned short *d = (unsigned short*)rc.data(); + for (int i = 0; i < src.length(); ++i) { + d[i] = qToLittleEndian(s[i]); + } + return rc; +} + + +static QString qStringFromUcs2Le(const QByteArray& src) +{ + Q_ASSERT(src.size() % 2 == 0); + unsigned short *d = (unsigned short*)src.data(); + for (int i = 0; i < src.length() / 2; ++i) { + d[i] = qFromLittleEndian(d[i]); + } + return QString((const QChar *)src.data(), src.size()/2); +} + +#ifdef NTLMV1_CLIENT +static QByteArray qEncodeNtlmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch) +{ + QCryptographicHash md4(QCryptographicHash::Md4); + QByteArray asUcs2Le = qStringAsUcs2Le(ctx->password); + md4.addData(asUcs2Le.data(), asUcs2Le.size()); + + unsigned char md4hash[22]; + memset(md4hash, 0, sizeof(md4hash)); + QByteArray hash = md4.result(); + Q_ASSERT(hash.size() == 16); + memcpy(md4hash, hash.constData(), 16); + + QByteArray rc(24, 0); + deshash((unsigned char *)rc.data(), md4hash, (unsigned char *)ch.challenge); + deshash((unsigned char *)rc.data() + 8, md4hash + 7, (unsigned char *)ch.challenge); + deshash((unsigned char *)rc.data() + 16, md4hash + 14, (unsigned char *)ch.challenge); + + hash.fill(0); + return rc; +} + + +static QByteArray qEncodeLmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch) +{ + QByteArray hash(21, 0); + QByteArray key(14, 0); + qstrncpy(key.data(), ctx->password.toUpper().toLatin1(), 14); + const char *block = "KGS!@#$%"; + + deshash((unsigned char *)hash.data(), (unsigned char *)key.data(), (unsigned char *)block); + deshash((unsigned char *)hash.data() + 8, (unsigned char *)key.data() + 7, (unsigned char *)block); + key.fill(0); + + QByteArray rc(24, 0); + deshash((unsigned char *)rc.data(), (unsigned char *)hash.data(), ch.challenge); + deshash((unsigned char *)rc.data() + 8, (unsigned char *)hash.data() + 7, ch.challenge); + deshash((unsigned char *)rc.data() + 16, (unsigned char *)hash.data() + 14, ch.challenge); + + hash.fill(0); + return rc; +} +#endif + +/********************************************************************* +* Function Name: qEncodeHmacMd5 +* Params: +* key: Type - QByteArray +* - It is the Authentication key +* message: Type - QByteArray +* - This is the actual message which will be encoded +* using HMacMd5 hash algorithm +* +* Return Value: +* hmacDigest: Type - QByteArray +* +* Description: +* This function will be used to encode the input message using +* HMacMd5 hash algorithm. +* +* As per the RFC2104 the HMacMd5 algorithm can be specified +* --------------------------------------- +* MD5(K XOR opad, MD5(K XOR ipad, text)) +* --------------------------------------- +* +*********************************************************************/ +QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message) +{ + Q_ASSERT_X(!(message.isEmpty()),"qEncodeHmacMd5", "Empty message check"); + Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check"); + + QCryptographicHash hash(QCryptographicHash::Md5); + QByteArray hMsg; + + QByteArray iKeyPad(blockSize, 0x36); + QByteArray oKeyPad(blockSize, 0x5c); + + hash.reset(); + // Adjust the key length to blockSize + + if(blockSize < key.length()) { + hash.addData(key); + key = hash.result(); //MD5 will always return 16 bytes length output + } + + //Key will be <= 16 or 20 bytes as hash function (MD5 or SHA hash algorithms) + //key size can be max of Block size only + key = key.leftJustified(blockSize,0,true); + + //iKeyPad, oKeyPad and key are all of same size "blockSize" + + //xor of iKeyPad with Key and store the result into iKeyPad + for(int i = 0; i<key.size();i++) { + iKeyPad[i] = key[i]^iKeyPad[i]; + } + + //xor of oKeyPad with Key and store the result into oKeyPad + for(int i = 0; i<key.size();i++) { + oKeyPad[i] = key[i]^oKeyPad[i]; + } + + iKeyPad.append(message); // (K0 xor ipad) || text + + hash.reset(); + hash.addData(iKeyPad); + hMsg = hash.result(); + //Digest gen after pass-1: H((K0 xor ipad)||text) + + QByteArray hmacDigest; + oKeyPad.append(hMsg); + hash.reset(); + hash.addData(oKeyPad); + hmacDigest = hash.result(); + // H((K0 xor opad )|| H((K0 xor ipad) || text)) + + /*hmacDigest should not be less than half the length of the HMAC output + (to match the birthday attack bound) and not less than 80 bits + (a suitable lower bound on the number of bits that need to be + predicted by an attacker). + Refer RFC 2104 for more details on truncation part */ + + /*MD5 hash always returns 16 byte digest only and HMAC-MD5 spec + (RFC 2104) also says digest length should be 16 bytes*/ + return hmacDigest; +} + +static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx, + QNtlmPhase3Block *phase3) +{ + Q_ASSERT(phase3 != 0); + // since v2 Hash is need for both NTLMv2 and LMv2 it is calculated + // only once and stored and reused + if(phase3->v2Hash.size() == 0) { + QCryptographicHash md4(QCryptographicHash::Md4); + QByteArray passUnicode = qStringAsUcs2Le(ctx->password); + md4.addData(passUnicode.data(), passUnicode.size()); + + QByteArray hashKey = md4.result(); + Q_ASSERT(hashKey.size() == 16); + // Assuming the user and domain is always unicode in challenge + QByteArray message = + qStringAsUcs2Le(ctx->extractedUser.toUpper()) + + qStringAsUcs2Le(phase3->domainStr); + + phase3->v2Hash = qEncodeHmacMd5(hashKey, message); + } + return phase3->v2Hash; +} + +static QByteArray clientChallenge(const QAuthenticatorPrivate *ctx) +{ + Q_ASSERT(ctx->cnonce.size() >= 8); + QByteArray clientCh = ctx->cnonce.right(8); + return clientCh; +} + +// caller has to ensure a valid targetInfoBuff +static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff) +{ + QByteArray timeArray; + QDataStream ds(targetInfoBuff); + ds.setByteOrder(QDataStream::LittleEndian); + + quint16 avId; + quint16 avLen; + + ds >> avId; + ds >> avLen; + while(avId != 0) { + if(avId == AVTIMESTAMP) { + timeArray.resize(avLen); + //avLen size of QByteArray is allocated + ds.readRawData(timeArray.data(), avLen); + break; + } + ds.skipRawData(avLen); + ds >> avId; + ds >> avLen; + } + return timeArray; +} + +static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx, + const QNtlmPhase2Block& ch, + QNtlmPhase3Block *phase3) +{ + Q_ASSERT(phase3 != 0); + // return value stored in phase3 + qCreatev2Hash(ctx, phase3); + + QByteArray temp; + QDataStream ds(&temp, QIODevice::WriteOnly); + ds.setByteOrder(QDataStream::LittleEndian); + + ds << respversion; + ds << hirespversion; + + //Reserved + QByteArray reserved1(6, 0); + ds.writeRawData(reserved1.constData(), reserved1.size()); + + quint64 time = 0; + QByteArray timeArray; + + if(ch.targetInfo.len) + { + timeArray = qExtractServerTime(ch.targetInfoBuff); + } + + //if server sends time, use it instead of current time + if(timeArray.size()) { + ds.writeRawData(timeArray.constData(), timeArray.size()); + } else { + QDateTime currentTime(QDate::currentDate(), + QTime::currentTime(), Qt::UTC); + + // number of seconds between 1601 and epoc(1970) + // 369 years, 89 leap years + // ((369 * 365) + 89) * 24 * 3600 = 11644473600 + + time = Q_UINT64_C(currentTime.toTime_t() + 11644473600); + + // represented as 100 nano seconds + time = Q_UINT64_C(time * 10000000); + ds << time; + } + + //8 byte client challenge + QByteArray clientCh = clientChallenge(ctx); + ds.writeRawData(clientCh.constData(), clientCh.size()); + + //Reserved + QByteArray reserved2(4, 0); + ds.writeRawData(reserved2.constData(), reserved2.size()); + + if (ch.targetInfo.len > 0) { + ds.writeRawData(ch.targetInfoBuff.constData(), + ch.targetInfoBuff.size()); + } + + //Reserved + QByteArray reserved3(4, 0); + ds.writeRawData(reserved3.constData(), reserved3.size()); + + QByteArray message((const char*)ch.challenge, sizeof(ch.challenge)); + message.append(temp); + + QByteArray ntChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message); + ntChallengeResp.append(temp); + + return ntChallengeResp; +} + +static QByteArray qEncodeLmv2Response(const QAuthenticatorPrivate *ctx, + const QNtlmPhase2Block& ch, + QNtlmPhase3Block *phase3) +{ + Q_ASSERT(phase3 != 0); + // return value stored in phase3 + qCreatev2Hash(ctx, phase3); + + QByteArray message((const char*)ch.challenge, sizeof(ch.challenge)); + QByteArray clientCh = clientChallenge(ctx); + + message.append(clientCh); + + QByteArray lmChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message); + lmChallengeResp.append(clientCh); + + return lmChallengeResp; +} + +static bool qNtlmDecodePhase2(const QByteArray& data, QNtlmPhase2Block& ch) +{ + Q_ASSERT(QNtlmPhase2BlockBase::Size == sizeof(QNtlmPhase2BlockBase)); + if (data.size() < QNtlmPhase2BlockBase::Size) + return false; + + + QDataStream ds(data); + ds.setByteOrder(QDataStream::LittleEndian); + if (ds.readRawData(ch.magic, 8) < 8) + return false; + if (strncmp(ch.magic, "NTLMSSP", 8) != 0) + return false; + + ds >> ch.type; + if (ch.type != 2) + return false; + + ds >> ch.targetName; + ds >> ch.flags; + if (ds.readRawData((char *)ch.challenge, 8) < 8) + return false; + ds >> ch.context[0] >> ch.context[1]; + ds >> ch.targetInfo; + + if (ch.targetName.len > 0) { + if (ch.targetName.len + ch.targetName.offset >= (unsigned)data.size()) + return false; + + ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len)); + } + + if (ch.targetInfo.len > 0) { + if (ch.targetInfo.len + ch.targetInfo.offset > (unsigned)data.size()) + return false; + + ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len); + } + + return true; +} + + +static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data) +{ + QNtlmPhase2Block ch; + if (!qNtlmDecodePhase2(phase2data, ch)) + return QByteArray(); + + QByteArray rc; + QDataStream ds(&rc, QIODevice::WriteOnly); + ds.setByteOrder(QDataStream::LittleEndian); + QNtlmPhase3Block pb; + + bool unicode = ch.flags & NTLMSSP_NEGOTIATE_UNICODE; + + pb.flags = NTLMSSP_NEGOTIATE_NTLM; + if (unicode) + pb.flags |= NTLMSSP_NEGOTIATE_UNICODE; + else + pb.flags |= NTLMSSP_NEGOTIATE_OEM; + + + int offset = QNtlmPhase3BlockBase::Size; + Q_ASSERT(QNtlmPhase3BlockBase::Size == sizeof(QNtlmPhase3BlockBase)); + + if(ctx->userDomain.isEmpty()) { + offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode); + pb.domainStr = ch.targetNameStr; + } else { + offset = qEncodeNtlmString(pb.domain, offset, ctx->userDomain, unicode); + pb.domainStr = ctx->userDomain; + } + + offset = qEncodeNtlmString(pb.user, offset, ctx->extractedUser, unicode); + pb.userStr = ctx->extractedUser; + + offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode); + pb.workstationStr = ctx->workstation; + + // Get LM response +#ifdef NTLMV1_CLIENT + pb.lmResponseBuf = qEncodeLmResponse(ctx, ch); +#else + if (ch.targetInfo.len > 0) { + pb.lmResponseBuf = QByteArray(); + } else { + pb.lmResponseBuf = qEncodeLmv2Response(ctx, ch, &pb); + } +#endif + offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf); + + // Get NTLM response +#ifdef NTLMV1_CLIENT + pb.ntlmResponseBuf = qEncodeNtlmResponse(ctx, ch); +#else + pb.ntlmResponseBuf = qEncodeNtlmv2Response(ctx, ch, &pb); +#endif + offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf); + + + // Encode and send + ds << pb; + + return rc; +} + + + +QT_END_NAMESPACE diff --git a/src/network/kernel/qauthenticator.h b/src/network/kernel/qauthenticator.h new file mode 100644 index 0000000000..b97802ad3e --- /dev/null +++ b/src/network/kernel/qauthenticator.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QAUTHENTICATOR_H +#define QAUTHENTICATOR_H + +#include <QtCore/qstring.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QAuthenticatorPrivate; +class QUrl; + +class Q_NETWORK_EXPORT QAuthenticator +{ +public: + QAuthenticator(); + ~QAuthenticator(); + + QAuthenticator(const QAuthenticator &other); + QAuthenticator &operator=(const QAuthenticator &other); + + bool operator==(const QAuthenticator &other) const; + inline bool operator!=(const QAuthenticator &other) const { return !operator==(other); } + + QString user() const; + void setUser(const QString &user); + + QString password() const; + void setPassword(const QString &password); + + QString realm() const; + + QVariant option(const QString &opt) const; + QVariantHash options() const; + void setOption(const QString &opt, const QVariant &value); + + bool isNull() const; + void detach(); +private: + friend class QAuthenticatorPrivate; + QAuthenticatorPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/kernel/qauthenticator_p.h b/src/network/kernel/qauthenticator_p.h new file mode 100644 index 0000000000..7db2dedc7e --- /dev/null +++ b/src/network/kernel/qauthenticator_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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 QAUTHENTICATOR_P_H +#define QAUTHENTICATOR_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 <qhash.h> +#include <qbytearray.h> +#include <qstring.h> +#include <qauthenticator.h> +#include <qvariant.h> + +QT_BEGIN_NAMESPACE + +class QHttpResponseHeader; + +class Q_AUTOTEST_EXPORT QAuthenticatorPrivate +{ +public: + enum Method { None, Basic, Plain, Login, Ntlm, CramMd5, DigestMd5 }; + QAuthenticatorPrivate(); + + QAtomicInt ref; + QString user; + QString extractedUser; + QString password; + QVariantHash options; + Method method; + QString realm; + QByteArray challenge; + + enum Phase { + Start, + Phase2, + Done, + Invalid + }; + Phase phase; + + // digest specific + QByteArray cnonce; + int nonceCount; + + // ntlm specific + QString workstation; + QString userDomain; + + QByteArray calculateResponse(const QByteArray &method, const QByteArray &path); + + inline static QAuthenticatorPrivate *getPrivate(QAuthenticator &auth) { return auth.d; } + inline static const QAuthenticatorPrivate *getPrivate(const QAuthenticator &auth) { return auth.d; } + + QByteArray digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path); + static QHash<QByteArray, QByteArray> parseDigestAuthenticationChallenge(const QByteArray &challenge); + +#ifndef QT_NO_HTTP + void parseHttpResponse(const QHttpResponseHeader &, bool isProxy); +#endif + void parseHttpResponse(const QList<QPair<QByteArray, QByteArray> >&, bool isProxy); + +}; + + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qhostaddress.cpp b/src/network/kernel/qhostaddress.cpp new file mode 100644 index 0000000000..ae7d7a1486 --- /dev/null +++ b/src/network/kernel/qhostaddress.cpp @@ -0,0 +1,1170 @@ +/**************************************************************************** +** +** 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 "qhostaddress.h" +#include "qhostaddress_p.h" +#include "qdebug.h" +#include "qplatformdefs.h" +#include "qstringlist.h" +#include "qendian.h" +#ifndef QT_NO_DATASTREAM +#include <qdatastream.h> +#endif +#if defined(Q_OS_WINCE) +#include <winsock.h> +#endif + +#ifdef QT_LINUXBASE +# include <arpa/inet.h> +#endif + +QT_BEGIN_NAMESPACE + +#define QT_ENSURE_PARSED(a) \ + do { \ + if (!(a)->d->isParsed) \ + (a)->d->parse(); \ + } while (0) + +#ifdef Q_OS_WIN +# if !defined (QT_NO_IPV6) +// sockaddr_in6 size changed between old and new SDK +// Only the new version is the correct one, so always +// use this structure. +#if defined(Q_OS_WINCE) +# if !defined(u_char) +# define u_char unsigned char +# endif +# if !defined(u_short) +# define u_short unsigned short +# endif +# if !defined(u_long) +# define u_long unsigned long +# endif +#endif +struct qt_in6_addr { + u_char qt_s6_addr[16]; +}; +typedef struct { + short sin6_family; /* AF_INET6 */ + u_short sin6_port; /* Transport level port number */ + u_long sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + u_long sin6_scope_id; /* set of interfaces for a scope */ +} qt_sockaddr_in6; +# else +typedef void * qt_sockaddr_in6 ; +# endif +# ifndef AF_INET6 +# define AF_INET6 23 /* Internetwork Version 6 */ +# endif +#else +#define qt_sockaddr_in6 sockaddr_in6 +#define qt_s6_addr s6_addr +#endif + + +class QHostAddressPrivate +{ +public: + QHostAddressPrivate(); + + void setAddress(quint32 a_ = 0); + void setAddress(const quint8 *a_); + void setAddress(const Q_IPV6ADDR &a_); + + bool parse(); + void clear(); + + quint32 a; // IPv4 address + Q_IPV6ADDR a6; // IPv6 address + QAbstractSocket::NetworkLayerProtocol protocol; + + QString ipString; + bool isParsed; + QString scopeId; + + friend class QHostAddress; +}; + +QHostAddressPrivate::QHostAddressPrivate() + : a(0), protocol(QAbstractSocket::UnknownNetworkLayerProtocol), isParsed(true) +{ + memset(&a6, 0, sizeof(a6)); +} + +void QHostAddressPrivate::setAddress(quint32 a_) +{ + a = a_; + protocol = QAbstractSocket::IPv4Protocol; + isParsed = true; +} + +void QHostAddressPrivate::setAddress(const quint8 *a_) +{ + for (int i = 0; i < 16; i++) + a6[i] = a_[i]; + protocol = QAbstractSocket::IPv6Protocol; + isParsed = true; +} + +void QHostAddressPrivate::setAddress(const Q_IPV6ADDR &a_) +{ + a6 = a_; + a = 0; + protocol = QAbstractSocket::IPv6Protocol; + isParsed = true; +} + +static bool parseIp4(const QString& address, quint32 *addr) +{ + QStringList ipv4 = address.split(QLatin1String(".")); + if (ipv4.count() != 4) + return false; + + quint32 ipv4Address = 0; + for (int i = 0; i < 4; ++i) { + bool ok = false; + uint byteValue = ipv4.at(i).toUInt(&ok); + if (!ok || byteValue > 255) + return false; + + ipv4Address <<= 8; + ipv4Address += byteValue; + } + + *addr = ipv4Address; + return true; +} + +static bool parseIp6(const QString &address, quint8 *addr, QString *scopeId) +{ + QString tmp = address; + int scopeIdPos = tmp.lastIndexOf(QLatin1Char('%')); + if (scopeIdPos != -1) { + *scopeId = tmp.mid(scopeIdPos + 1); + tmp.chop(tmp.size() - scopeIdPos); + } else { + scopeId->clear(); + } + + QStringList ipv6 = tmp.split(QLatin1String(":")); + int count = ipv6.count(); + if (count < 3 || count > 8) + return false; + + int colonColon = tmp.count(QLatin1String("::")); + if(count == 8 && colonColon > 1) + return false; + + // address can be compressed with a "::", but that + // may only appear once (see RFC 1884) + // the statement below means: + // if(shortened notation is not used AND + // ((pure IPv6 notation AND less than 8 parts) OR + // ((mixed IPv4/6 notation AND less than 7 parts))) + if(colonColon != 1 && count < (tmp.contains(QLatin1Char('.')) ? 7 : 8)) + return false; + + int mc = 16; + int fillCount = 9 - count; // number of 0 words to fill in the middle + for (int i = count - 1; i >= 0; --i) { + if (mc <= 0) + return false; + + if (ipv6.at(i).isEmpty()) { + if (i == count - 1) { + // special case: ":" is last character + if (!ipv6.at(i - 1).isEmpty()) + return false; + addr[--mc] = 0; + addr[--mc] = 0; + } else if (i == 0) { + // special case: ":" is first character + if (!ipv6.at(i + 1).isEmpty()) + return false; + addr[--mc] = 0; + addr[--mc] = 0; + } else { + for (int j = 0; j < fillCount; ++j) { + if (mc <= 0) + return false; + addr[--mc] = 0; + addr[--mc] = 0; + } + } + } else { + bool ok = false; + uint byteValue = ipv6.at(i).toUInt(&ok, 16); + if (ok && byteValue <= 0xffff) { + addr[--mc] = byteValue & 0xff; + addr[--mc] = (byteValue >> 8) & 0xff; + } else { + if (i != count - 1) + return false; + + // parse the ipv4 part of a mixed type + quint32 maybeIp4; + if (!parseIp4(ipv6.at(i), &maybeIp4)) + return false; + + addr[--mc] = maybeIp4 & 0xff; + addr[--mc] = (maybeIp4 >> 8) & 0xff; + addr[--mc] = (maybeIp4 >> 16) & 0xff; + addr[--mc] = (maybeIp4 >> 24) & 0xff; + --fillCount; + } + } + } + + return true; +} + +bool QHostAddressPrivate::parse() +{ + isParsed = true; + protocol = QAbstractSocket::UnknownNetworkLayerProtocol; + QString a = ipString.simplified(); + + // All IPv6 addresses contain a ':', and may contain a '.'. + if (a.contains(QLatin1Char(':'))) { + quint8 maybeIp6[16]; + if (parseIp6(a, maybeIp6, &scopeId)) { + setAddress(maybeIp6); + protocol = QAbstractSocket::IPv6Protocol; + return true; + } + } + + // All IPv4 addresses contain a '.'. + if (a.contains(QLatin1Char('.'))) { + quint32 maybeIp4 = 0; + if (parseIp4(a, &maybeIp4)) { + setAddress(maybeIp4); + protocol = QAbstractSocket::IPv4Protocol; + return true; + } + } + + return false; +} + +void QHostAddressPrivate::clear() +{ + a = 0; + protocol = QAbstractSocket::UnknownNetworkLayerProtocol; + isParsed = true; + memset(&a6, 0, sizeof(a6)); +} + + +bool QNetmaskAddress::setAddress(const QString &address) +{ + length = -1; + QHostAddress other; + return other.setAddress(address) && setAddress(other); +} + +bool QNetmaskAddress::setAddress(const QHostAddress &address) +{ + static const quint8 zeroes[16] = { 0 }; + union { + quint32 v4; + quint8 v6[16]; + } ip; + + int netmask = 0; + quint8 *ptr = ip.v6; + quint8 *end; + length = -1; + + QHostAddress::operator=(address); + + if (d->protocol == QAbstractSocket::IPv4Protocol) { + ip.v4 = qToBigEndian(d->a); + end = ptr + 4; + } else if (d->protocol == QAbstractSocket::IPv6Protocol) { + memcpy(ip.v6, d->a6.c, 16); + end = ptr + 16; + } else { + d->clear(); + return false; + } + + while (ptr < end) { + switch (*ptr) { + case 255: + netmask += 8; + ++ptr; + continue; + + default: + d->clear(); + return false; // invalid IP-style netmask + + // the rest always falls through + case 254: + ++netmask; + case 252: + ++netmask; + case 248: + ++netmask; + case 240: + ++netmask; + case 224: + ++netmask; + case 192: + ++netmask; + case 128: + ++netmask; + case 0: + break; + } + break; + } + + // confirm that the rest is only zeroes + if (ptr < end && memcmp(ptr + 1, zeroes, end - ptr - 1) != 0) { + d->clear(); + return false; + } + + length = netmask; + return true; +} + +static void clearBits(quint8 *where, int start, int end) +{ + Q_ASSERT(end == 32 || end == 128); + if (start == end) + return; + + // for the byte where 'start' is, clear the lower bits only + quint8 bytemask = 256 - (1 << (8 - (start & 7))); + where[start / 8] &= bytemask; + + // for the tail part, clear everything + memset(where + (start + 7) / 8, 0, end / 8 - (start + 7) / 8); +} + +int QNetmaskAddress::prefixLength() const +{ + return length; +} + +void QNetmaskAddress::setPrefixLength(QAbstractSocket::NetworkLayerProtocol proto, int newLength) +{ + length = newLength; + if (length < 0 || length > (proto == QAbstractSocket::IPv4Protocol ? 32 : + proto == QAbstractSocket::IPv6Protocol ? 128 : -1)) { + // invalid information, reject + d->protocol = QAbstractSocket::UnknownNetworkLayerProtocol; + length = -1; + return; + } + + d->protocol = proto; + if (d->protocol == QAbstractSocket::IPv4Protocol) { + if (length == 0) { + d->a = 0; + } else if (length == 32) { + d->a = quint32(0xffffffff); + } else { + d->a = quint32(0xffffffff) >> (32 - length) << (32 - length); + } + } else { + memset(d->a6.c, 0xFF, sizeof(d->a6)); + clearBits(d->a6.c, length, 128); + } +} + +/*! + \class QHostAddress + \brief The QHostAddress class provides an IP address. + \ingroup network + \inmodule QtNetwork + + This class holds an IPv4 or IPv6 address in a platform- and + protocol-independent manner. + + QHostAddress is normally used with the QTcpSocket, QTcpServer, + and QUdpSocket to connect to a host or to set up a server. + + A host address is set with setAddress(), and retrieved with + toIPv4Address(), toIPv6Address(), or toString(). You can check the + type with protocol(). + + \note Please note that QHostAddress does not do DNS lookups. + QHostInfo is needed for that. + + The class also supports common predefined addresses: \l Null, \l + LocalHost, \l LocalHostIPv6, \l Broadcast, and \l Any. + + \sa QHostInfo, QTcpSocket, QTcpServer, QUdpSocket +*/ + +/*! \enum QHostAddress::SpecialAddress + + \value Null The null address object. Equivalent to QHostAddress(). + \value LocalHost The IPv4 localhost address. Equivalent to QHostAddress("127.0.0.1"). + \value LocalHostIPv6 The IPv6 localhost address. Equivalent to QHostAddress("::1"). + \value Broadcast The IPv4 broadcast address. Equivalent to QHostAddress("255.255.255.255"). + \value Any The IPv4 any-address. Equivalent to QHostAddress("0.0.0.0"). + \value AnyIPv6 The IPv6 any-address. Equivalent to QHostAddress("::"). +*/ + +/*! Constructs a host address object with the IP address 0.0.0.0. + + \sa clear() +*/ +QHostAddress::QHostAddress() + : d(new QHostAddressPrivate) +{ +} + +/*! + Constructs a host address object with the IPv4 address \a ip4Addr. +*/ +QHostAddress::QHostAddress(quint32 ip4Addr) + : d(new QHostAddressPrivate) +{ + setAddress(ip4Addr); +} + +/*! + Constructs a host address object with the IPv6 address \a ip6Addr. + + \a ip6Addr must be a 16-byte array in network byte order (big + endian). +*/ +QHostAddress::QHostAddress(quint8 *ip6Addr) + : d(new QHostAddressPrivate) +{ + setAddress(ip6Addr); +} + +/*! + Constructs a host address object with the IPv6 address \a ip6Addr. +*/ +QHostAddress::QHostAddress(const Q_IPV6ADDR &ip6Addr) + : d(new QHostAddressPrivate) +{ + setAddress(ip6Addr); +} + +/*! + Constructs an IPv4 or IPv6 address based on the string \a address + (e.g., "127.0.0.1"). + + \sa setAddress() +*/ +QHostAddress::QHostAddress(const QString &address) + : d(new QHostAddressPrivate) +{ + d->ipString = address; + d->isParsed = false; +} + +/*! + \fn QHostAddress::QHostAddress(const sockaddr *sockaddr) + + Constructs an IPv4 or IPv6 address using the address specified by + the native structure \a sockaddr. + + \sa setAddress() +*/ +QHostAddress::QHostAddress(const struct sockaddr *sockaddr) + : d(new QHostAddressPrivate) +{ + if (sockaddr->sa_family == AF_INET) + setAddress(htonl(((sockaddr_in *)sockaddr)->sin_addr.s_addr)); +#ifndef QT_NO_IPV6 + else if (sockaddr->sa_family == AF_INET6) + setAddress(((qt_sockaddr_in6 *)sockaddr)->sin6_addr.qt_s6_addr); +#endif +} + +/*! + Constructs a copy of the given \a address. +*/ +QHostAddress::QHostAddress(const QHostAddress &address) + : d(new QHostAddressPrivate(*address.d.data())) +{ +} + +/*! + Constructs a QHostAddress object for \a address. +*/ +QHostAddress::QHostAddress(SpecialAddress address) + : d(new QHostAddressPrivate) +{ + switch (address) { + case Null: + break; + case Broadcast: + setAddress(QLatin1String("255.255.255.255")); + break; + case LocalHost: + setAddress(QLatin1String("127.0.0.1")); + break; + case LocalHostIPv6: + setAddress(QLatin1String("::1")); + break; + case Any: + setAddress(QLatin1String("0.0.0.0")); + break; + case AnyIPv6: + setAddress(QLatin1String("::")); + break; + } +} + +/*! + Destroys the host address object. +*/ +QHostAddress::~QHostAddress() +{ +} + +/*! + Assigns another host \a address to this object, and returns a reference + to this object. +*/ +QHostAddress &QHostAddress::operator=(const QHostAddress &address) +{ + *d.data() = *address.d.data(); + return *this; +} + +/*! + Assigns the host address \a address to this object, and returns a + reference to this object. + + \sa setAddress() +*/ +QHostAddress &QHostAddress::operator=(const QString &address) +{ + setAddress(address); + return *this; +} + +/*! + \fn bool QHostAddress::operator!=(const QHostAddress &other) const + \since 4.2 + + Returns true if this host address is not the same as the \a other + address given; otherwise returns false. +*/ + +/*! + \fn bool QHostAddress::operator!=(SpecialAddress other) const + + Returns true if this host address is not the same as the \a other + address given; otherwise returns false. +*/ + +/*! + Sets the host address to 0.0.0.0. +*/ +void QHostAddress::clear() +{ + d->clear(); +} + +/*! + Set the IPv4 address specified by \a ip4Addr. +*/ +void QHostAddress::setAddress(quint32 ip4Addr) +{ + d->setAddress(ip4Addr); +} + +/*! + \overload + + Set the IPv6 address specified by \a ip6Addr. + + \a ip6Addr must be an array of 16 bytes in network byte order + (high-order byte first). +*/ +void QHostAddress::setAddress(quint8 *ip6Addr) +{ + d->setAddress(ip6Addr); +} + +/*! + \overload + + Set the IPv6 address specified by \a ip6Addr. +*/ +void QHostAddress::setAddress(const Q_IPV6ADDR &ip6Addr) +{ + d->setAddress(ip6Addr); +} + +/*! + \overload + + Sets the IPv4 or IPv6 address specified by the string + representation specified by \a address (e.g. "127.0.0.1"). + Returns true and sets the address if the address was successfully + parsed; otherwise returns false. +*/ +bool QHostAddress::setAddress(const QString &address) +{ + d->ipString = address; + return d->parse(); +} + +/*! + \fn void QHostAddress::setAddress(const sockaddr *sockaddr) + \overload + + Sets the IPv4 or IPv6 address specified by the native structure \a + sockaddr. Returns true and sets the address if the address was + successfully parsed; otherwise returns false. +*/ +void QHostAddress::setAddress(const struct sockaddr *sockaddr) +{ + clear(); + if (sockaddr->sa_family == AF_INET) + setAddress(htonl(((sockaddr_in *)sockaddr)->sin_addr.s_addr)); +#ifndef QT_NO_IPV6 + else if (sockaddr->sa_family == AF_INET6) + setAddress(((qt_sockaddr_in6 *)sockaddr)->sin6_addr.qt_s6_addr); +#endif +} + +/*! + Returns the IPv4 address as a number. + + For example, if the address is 127.0.0.1, the returned value is + 2130706433 (i.e. 0x7f000001). + + This value is only valid if the Protocol() is + \l{QAbstractSocket::}{IPv4Protocol}. + + \sa toString() +*/ +quint32 QHostAddress::toIPv4Address() const +{ + QT_ENSURE_PARSED(this); + return d->a; +} + +/*! + Returns the network layer protocol of the host address. +*/ +QAbstractSocket::NetworkLayerProtocol QHostAddress::protocol() const +{ + QT_ENSURE_PARSED(this); + return d->protocol; +} + +/*! + Returns the IPv6 address as a Q_IPV6ADDR structure. The structure + consists of 16 unsigned characters. + + \snippet doc/src/snippets/code/src_network_kernel_qhostaddress.cpp 0 + + This value is only valid if the protocol() is + \l{QAbstractSocket::}{IPv6Protocol}. + + \sa toString() +*/ +Q_IPV6ADDR QHostAddress::toIPv6Address() const +{ + QT_ENSURE_PARSED(this); + return d->a6; +} + +/*! + Returns the address as a string. + + For example, if the address is the IPv4 address 127.0.0.1, the + returned string is "127.0.0.1". + + \sa toIPv4Address() +*/ +QString QHostAddress::toString() const +{ + QT_ENSURE_PARSED(this); + if (d->protocol == QAbstractSocket::IPv4Protocol) { + quint32 i = toIPv4Address(); + QString s; + s.sprintf("%d.%d.%d.%d", (i>>24) & 0xff, (i>>16) & 0xff, + (i >> 8) & 0xff, i & 0xff); + return s; + } + + if (d->protocol == QAbstractSocket::IPv6Protocol) { + quint16 ugle[8]; + for (int i = 0; i < 8; i++) { + ugle[i] = (quint16(d->a6[2*i]) << 8) | quint16(d->a6[2*i+1]); + } + QString s; + s.sprintf("%X:%X:%X:%X:%X:%X:%X:%X", + ugle[0], ugle[1], ugle[2], ugle[3], ugle[4], ugle[5], ugle[6], ugle[7]); + if (!d->scopeId.isEmpty()) + s.append(QLatin1Char('%') + d->scopeId); + return s; + } + + return QString(); +} + +/*! + \since 4.1 + + Returns the scope ID of an IPv6 address. For IPv4 addresses, or if the + address does not contain a scope ID, an empty QString is returned. + + The IPv6 scope ID specifies the scope of \e reachability for non-global + IPv6 addresses, limiting the area in which the address can be used. All + IPv6 addresses are associated with such a reachability scope. The scope ID + is used to disambiguate addresses that are not guaranteed to be globally + unique. + + IPv6 specifies the following four levels of reachability: + + \list + + \o Node-local: Addresses that are only used for communicating with + services on the same interface (e.g., the loopback interface "::1"). + + \o Link-local: Addresses that are local to the network interface + (\e{link}). There is always one link-local address for each IPv6 interface + on your host. Link-local addresses ("fe80...") are generated from the MAC + address of the local network adaptor, and are not guaranteed to be unique. + + \o Site-local: Addresses that are local to the site / private network + (e.g., the company intranet). Site-local addresses ("fec0...") are + usually distributed by the site router, and are not guaranteed to be + unique outside of the local site. + + \o Global: For globally routable addresses, such as public servers on the + Internet. + + \endlist + + When using a link-local or site-local address for IPv6 connections, you + must specify the scope ID. The scope ID for a link-local address is + usually the same as the interface name (e.g., "eth0", "en1") or number + (e.g., "1", "2"). + + \sa setScopeId() +*/ +QString QHostAddress::scopeId() const +{ + QT_ENSURE_PARSED(this); + return (d->protocol == QAbstractSocket::IPv6Protocol) ? d->scopeId : QString(); +} + +/*! + \since 4.1 + + Sets the IPv6 scope ID of the address to \a id. If the address + protocol is not IPv6, this function does nothing. +*/ +void QHostAddress::setScopeId(const QString &id) +{ + QT_ENSURE_PARSED(this); + if (d->protocol == QAbstractSocket::IPv6Protocol) + d->scopeId = id; +} + +/*! + Returns true if this host address is the same as the \a other address + given; otherwise returns false. +*/ +bool QHostAddress::operator==(const QHostAddress &other) const +{ + QT_ENSURE_PARSED(this); + QT_ENSURE_PARSED(&other); + + if (d->protocol == QAbstractSocket::IPv4Protocol) + return other.d->protocol == QAbstractSocket::IPv4Protocol && d->a == other.d->a; + if (d->protocol == QAbstractSocket::IPv6Protocol) { + return other.d->protocol == QAbstractSocket::IPv6Protocol + && memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0; + } + return d->protocol == other.d->protocol; +} + +/*! + Returns true if this host address is the same as the \a other + address given; otherwise returns false. +*/ +bool QHostAddress::operator ==(SpecialAddress other) const +{ + QT_ENSURE_PARSED(this); + QHostAddress otherAddress(other); + QT_ENSURE_PARSED(&otherAddress); + + if (d->protocol == QAbstractSocket::IPv4Protocol) + return otherAddress.d->protocol == QAbstractSocket::IPv4Protocol && d->a == otherAddress.d->a; + if (d->protocol == QAbstractSocket::IPv6Protocol) { + return otherAddress.d->protocol == QAbstractSocket::IPv6Protocol + && memcmp(&d->a6, &otherAddress.d->a6, sizeof(Q_IPV6ADDR)) == 0; + } + return int(other) == int(Null); +} + +/*! + Returns true if this host address is null (INADDR_ANY or in6addr_any). + The default constructor creates a null address, and that address is + not valid for any host or interface. +*/ +bool QHostAddress::isNull() const +{ + QT_ENSURE_PARSED(this); + return d->protocol == QAbstractSocket::UnknownNetworkLayerProtocol; +} + +/*! + \fn quint32 QHostAddress::ip4Addr() const + + Use toIPv4Address() instead. +*/ + +/*! + \fn bool QHostAddress::isIp4Addr() const + + Use protocol() instead. +*/ + +/*! + \fn bool QHostAddress::isIPv4Address() const + + Use protocol() instead. +*/ + +/*! + \fn bool QHostAddress::isIPv6Address() const + + Use protocol() instead. +*/ + +/*! + \since 4.5 + + Returns true if this IP is in the subnet described by the network + prefix \a subnet and netmask \a netmask. + + An IP is considered to belong to a subnet if it is contained + between the lowest and the highest address in that subnet. In the + case of IP version 4, the lowest address is the network address, + while the highest address is the broadcast address. + + The \a subnet argument does not have to be the actual network + address (the lowest address in the subnet). It can be any valid IP + belonging to that subnet. In particular, if it is equal to the IP + address held by this object, this function will always return true + (provided the netmask is a valid value). + + \sa parseSubnet() +*/ +bool QHostAddress::isInSubnet(const QHostAddress &subnet, int netmask) const +{ + QT_ENSURE_PARSED(this); + if (subnet.protocol() != d->protocol || netmask < 0) + return false; + + union { + quint32 ip; + quint8 data[4]; + } ip4, net4; + const quint8 *ip; + const quint8 *net; + if (d->protocol == QAbstractSocket::IPv4Protocol) { + if (netmask > 32) + netmask = 32; + ip4.ip = qToBigEndian(d->a); + net4.ip = qToBigEndian(subnet.d->a); + ip = ip4.data; + net = net4.data; + } else if (d->protocol == QAbstractSocket::IPv6Protocol) { + if (netmask > 128) + netmask = 128; + ip = d->a6.c; + net = subnet.d->a6.c; + } else { + return false; + } + + if (netmask >= 8 && memcmp(ip, net, netmask / 8) != 0) + return false; + if ((netmask & 7) == 0) + return true; + + // compare the last octet now + quint8 bytemask = 256 - (1 << (8 - (netmask & 7))); + quint8 ipbyte = ip[netmask / 8]; + quint8 netbyte = net[netmask / 8]; + return (ipbyte & bytemask) == (netbyte & bytemask); +} + +/*! + \since 4.5 + \overload + + Returns true if this IP is in the subnet described by \a + subnet. The QHostAddress member of \a subnet contains the network + prefix and the int (second) member contains the netmask (prefix + length). +*/ +bool QHostAddress::isInSubnet(const QPair<QHostAddress, int> &subnet) const +{ + return isInSubnet(subnet.first, subnet.second); +} + + +/*! + \since 4.5 + + Parses the IP and subnet information contained in \a subnet and + returns the network prefix for that network and its prefix length. + + The IP address and the netmask must be separated by a slash + (/). + + This function supports arguments in the form: + \list + \o 123.123.123.123/n where n is any value between 0 and 32 + \o 123.123.123.123/255.255.255.255 + \o <ipv6-address>/n where n is any value between 0 and 128 + \endlist + + For IP version 4, this function accepts as well missing trailing + components (i.e., less than 4 octets, like "192.168.1"), followed + or not by a dot. If the netmask is also missing in that case, it + is set to the number of octets actually passed (in the example + above, it would be 24, for 3 octets). + + \sa isInSubnet() +*/ +QPair<QHostAddress, int> QHostAddress::parseSubnet(const QString &subnet) +{ + // We support subnets in the form: + // ddd.ddd.ddd.ddd/nn + // ddd.ddd.ddd/nn + // ddd.ddd/nn + // ddd/nn + // ddd.ddd.ddd. + // ddd.ddd.ddd + // ddd.ddd. + // ddd.ddd + // ddd. + // ddd + // <ipv6-address>/nn + // + // where nn can be an IPv4-style netmask for the IPv4 forms + + const QPair<QHostAddress, int> invalid = qMakePair(QHostAddress(), -1); + if (subnet.isEmpty()) + return invalid; + + int slash = subnet.indexOf(QLatin1Char('/')); + QString netStr = subnet; + if (slash != -1) + netStr.truncate(slash); + + int netmask = -1; + bool isIpv6 = netStr.contains(QLatin1Char(':')); + + if (slash != -1) { + // is the netmask given in IP-form or in bit-count form? + if (!isIpv6 && subnet.indexOf(QLatin1Char('.'), slash + 1) != -1) { + // IP-style, convert it to bit-count form + QNetmaskAddress parser; + if (!parser.setAddress(subnet.mid(slash + 1))) + return invalid; + netmask = parser.prefixLength(); + } else { + bool ok; + netmask = subnet.mid(slash + 1).toUInt(&ok); + if (!ok) + return invalid; // failed to parse the subnet + } + } + + if (isIpv6) { + // looks like it's an IPv6 address + if (netmask > 128) + return invalid; // invalid netmask + if (netmask < 0) + netmask = 128; + + QHostAddress net; + if (!net.setAddress(netStr)) + return invalid; // failed to parse the IP + + clearBits(net.d->a6.c, netmask, 128); + return qMakePair(net, netmask); + } + + if (netmask > 32) + return invalid; // invalid netmask + + // parse the address manually + QStringList parts = netStr.split(QLatin1Char('.')); + if (parts.isEmpty() || parts.count() > 4) + return invalid; // invalid IPv4 address + + if (parts.last().isEmpty()) + parts.removeLast(); + + quint32 addr = 0; + for (int i = 0; i < parts.count(); ++i) { + bool ok; + uint byteValue = parts.at(i).toUInt(&ok); + if (!ok || byteValue > 255) + return invalid; // invalid IPv4 address + + addr <<= 8; + addr += byteValue; + } + addr <<= 8 * (4 - parts.count()); + if (netmask == -1) { + netmask = 8 * parts.count(); + } else if (netmask == 0) { + // special case here + // x86's instructions "shr" and "shl" do not operate when + // their argument is 32, so the code below doesn't work as expected + addr = 0; + } else if (netmask != 32) { + // clear remaining bits + quint32 mask = quint32(0xffffffff) >> (32 - netmask) << (32 - netmask); + addr &= mask; + } + + return qMakePair(QHostAddress(addr), netmask); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QHostAddress &address) +{ + d.maybeSpace() << "QHostAddress(" << address.toString() << ')'; + return d.space(); +} +#endif + +uint qHash(const QHostAddress &key) +{ + return qHash(key.toString()); +} + +#ifndef QT_NO_DATASTREAM + +/*! \relates QHostAddress + + Writes host address \a address to the stream \a out and returns a reference + to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QHostAddress &address) +{ + qint8 prot; + prot = qint8(address.protocol()); + out << prot; + switch (address.protocol()) { + case QAbstractSocket::UnknownNetworkLayerProtocol: + break; + case QAbstractSocket::IPv4Protocol: + out << address.toIPv4Address(); + break; + case QAbstractSocket::IPv6Protocol: + { + Q_IPV6ADDR ipv6 = address.toIPv6Address(); + for (int i = 0; i < 16; ++i) + out << ipv6[i]; + out << address.scopeId(); + } + break; + } + return out; +} + +/*! \relates QHostAddress + + Reads a host address into \a address from the stream \a in and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &in, QHostAddress &address) +{ + qint8 prot; + in >> prot; + switch (QAbstractSocket::NetworkLayerProtocol(prot)) { + case QAbstractSocket::UnknownNetworkLayerProtocol: + address.clear(); + break; + case QAbstractSocket::IPv4Protocol: + { + quint32 ipv4; + in >> ipv4; + address.setAddress(ipv4); + } + break; + case QAbstractSocket::IPv6Protocol: + { + Q_IPV6ADDR ipv6; + for (int i = 0; i < 16; ++i) + in >> ipv6[i]; + address.setAddress(ipv6); + + QString scope; + in >> scope; + address.setScopeId(scope); + } + break; + default: + address.clear(); + in.setStatus(QDataStream::ReadCorruptData); + } + return in; +} + +#endif //QT_NO_DATASTREAM + +QT_END_NAMESPACE diff --git a/src/network/kernel/qhostaddress.h b/src/network/kernel/qhostaddress.h new file mode 100644 index 0000000000..e626e9fefc --- /dev/null +++ b/src/network/kernel/qhostaddress.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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 QHOSTADDRESS_H +#define QHOSTADDRESS_H + +#include <QtCore/qpair.h> +#include <QtCore/qstring.h> +#include <QtCore/qscopedpointer.h> +#include <QtNetwork/qabstractsocket.h> + +struct sockaddr; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddressPrivate; + +class Q_NETWORK_EXPORT QIPv6Address +{ +public: + inline quint8 &operator [](int index) { return c[index]; } + inline quint8 operator [](int index) const { return c[index]; } + quint8 c[16]; +}; + +typedef QIPv6Address Q_IPV6ADDR; + +class Q_NETWORK_EXPORT QHostAddress +{ +public: + enum SpecialAddress { + Null, + Broadcast, + LocalHost, + LocalHostIPv6, + Any, + AnyIPv6 + }; + + QHostAddress(); + explicit QHostAddress(quint32 ip4Addr); + explicit QHostAddress(quint8 *ip6Addr); + explicit QHostAddress(const Q_IPV6ADDR &ip6Addr); + explicit QHostAddress(const sockaddr *sockaddr); + explicit QHostAddress(const QString &address); + QHostAddress(const QHostAddress ©); + QHostAddress(SpecialAddress address); + ~QHostAddress(); + + QHostAddress &operator=(const QHostAddress &other); + QHostAddress &operator=(const QString &address); + + void setAddress(quint32 ip4Addr); + void setAddress(quint8 *ip6Addr); + void setAddress(const Q_IPV6ADDR &ip6Addr); + void setAddress(const sockaddr *sockaddr); + bool setAddress(const QString &address); + + QAbstractSocket::NetworkLayerProtocol protocol() const; + quint32 toIPv4Address() const; + Q_IPV6ADDR toIPv6Address() const; + + QString toString() const; + + QString scopeId() const; + void setScopeId(const QString &id); + + bool operator ==(const QHostAddress &address) const; + bool operator ==(SpecialAddress address) const; + inline bool operator !=(const QHostAddress &address) const + { return !operator==(address); } + inline bool operator !=(SpecialAddress address) const + { return !operator==(address); } + bool isNull() const; + void clear(); + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT quint32 ip4Addr() const { return toIPv4Address(); } + inline QT3_SUPPORT bool isIPv4Address() const { return protocol() == QAbstractSocket::IPv4Protocol + || protocol() == QAbstractSocket::UnknownNetworkLayerProtocol; } + inline QT3_SUPPORT bool isIp4Addr() const { return protocol() == QAbstractSocket::IPv4Protocol + || protocol() == QAbstractSocket::UnknownNetworkLayerProtocol; } + inline QT3_SUPPORT bool isIPv6Address() const { return protocol() == QAbstractSocket::IPv6Protocol; } +#endif + + bool isInSubnet(const QHostAddress &subnet, int netmask) const; + bool isInSubnet(const QPair<QHostAddress, int> &subnet) const; + + static QPair<QHostAddress, int> parseSubnet(const QString &subnet); + +protected: + QScopedPointer<QHostAddressPrivate> d; +}; + +inline bool operator ==(QHostAddress::SpecialAddress address1, const QHostAddress &address2) +{ return address2 == address1; } + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug, const QHostAddress &); +#endif + + +Q_NETWORK_EXPORT uint qHash(const QHostAddress &key); + +#ifndef QT_NO_DATASTREAM +Q_NETWORK_EXPORT QDataStream &operator<<(QDataStream &, const QHostAddress &); +Q_NETWORK_EXPORT QDataStream &operator>>(QDataStream &, QHostAddress &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER +#endif // QHOSTADDRESS_H diff --git a/src/network/kernel/qhostaddress_p.h b/src/network/kernel/qhostaddress_p.h new file mode 100644 index 0000000000..a23b84ec69 --- /dev/null +++ b/src/network/kernel/qhostaddress_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 QHOSTADDRESSPRIVATE_H +#define QHOSTADDRESSPRIVATE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QHostAddress and QNetworkInterface classes. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +#include "qhostaddress.h" +#include "qabstractsocket.h" + +class QNetmaskAddress: public QHostAddress +{ + int length; +public: + QNetmaskAddress() : QHostAddress(), length(-1) { } + + bool setAddress(const QString &address); + bool setAddress(const QHostAddress &address); + + int prefixLength() const; + void setPrefixLength(QAbstractSocket::NetworkLayerProtocol proto, int len); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp new file mode 100644 index 0000000000..a16d4ca451 --- /dev/null +++ b/src/network/kernel/qhostinfo.cpp @@ -0,0 +1,808 @@ +/**************************************************************************** +** +** 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 "qhostinfo.h" +#include "qhostinfo_p.h" + +#include "QtCore/qscopedpointer.h" +#include <qabstracteventdispatcher.h> +#include <qcoreapplication.h> +#include <qmetaobject.h> +#include <qstringlist.h> +#include <qthread.h> +#include <qurl.h> +#include <private/qnetworksession_p.h> + +#ifdef Q_OS_UNIX +# include <unistd.h> +#endif + +QT_BEGIN_NAMESPACE + +//#define QHOSTINFO_DEBUG + +#ifndef Q_OS_SYMBIAN +Q_GLOBAL_STATIC(QHostInfoLookupManager, theHostInfoLookupManager) +#else +Q_GLOBAL_STATIC(QSymbianHostInfoLookupManager, theHostInfoLookupManager) +#endif + +/*! + \class QHostInfo + \brief The QHostInfo class provides static functions for host name lookups. + + \reentrant + \inmodule QtNetwork + \ingroup network + + QHostInfo uses the lookup mechanisms provided by the operating + system to find the IP address(es) associated with a host name, + or the host name associated with an IP address. + The class provides two static convenience functions: one that + works asynchronously and emits a signal once the host is found, + and one that blocks and returns a QHostInfo object. + + To look up a host's IP addresses asynchronously, call lookupHost(), + which takes the host name or IP address, a receiver object, and a slot + signature as arguments and returns an ID. You can abort the + lookup by calling abortHostLookup() with the lookup ID. + + Example: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 0 + + + The slot is invoked when the results are ready. The results are + stored in a QHostInfo object. Call + addresses() to get the list of IP addresses for the host, and + hostName() to get the host name that was looked up. + + If the lookup failed, error() returns the type of error that + occurred. errorString() gives a human-readable description of the + lookup error. + + If you want a blocking lookup, use the QHostInfo::fromName() function: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 1 + + QHostInfo supports Internationalized Domain Names (IDNs) through the + IDNA and Punycode standards. + + To retrieve the name of the local host, use the static + QHostInfo::localHostName() function. + + \note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup + instead of one dedicated DNS thread. This improves performance, + but also changes the order of signal emissions when using lookupHost() + compared to previous versions of Qt. + \note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache + for performance improvements. + + \sa QAbstractSocket, {http://www.rfc-editor.org/rfc/rfc3492.txt}{RFC 3492} +*/ + +static QBasicAtomicInt theIdCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +/*! + Looks up the IP address(es) associated with host name \a name, and + returns an ID for the lookup. When the result of the lookup is + ready, the slot or signal \a member in \a receiver is called with + a QHostInfo argument. The QHostInfo object can then be inspected + to get the results of the lookup. + + The lookup is performed by a single function call, for example: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 2 + + The implementation of the slot prints basic information about the + addresses returned by the lookup, or reports an error if it failed: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 3 + + If you pass a literal IP address to \a name instead of a host name, + QHostInfo will search for the domain name for the IP (i.e., QHostInfo will + perform a \e reverse lookup). On success, the resulting QHostInfo will + contain both the resolved domain name and IP addresses for the host + name. Example: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 4 + + \note There is no guarantee on the order the signals will be emitted + if you start multiple requests with lookupHost(). + + \sa abortHostLookup(), addresses(), error(), fromName() +*/ +int QHostInfo::lookupHost(const QString &name, QObject *receiver, + const char *member) +{ +#if defined QHOSTINFO_DEBUG + qDebug("QHostInfo::lookupHost(\"%s\", %p, %s)", + name.toLatin1().constData(), receiver, member ? member + 1 : 0); +#endif + + if (!QAbstractEventDispatcher::instance(QThread::currentThread())) { + qWarning("QHostInfo::lookupHost() called with no event dispatcher"); + return -1; + } + + qRegisterMetaType<QHostInfo>("QHostInfo"); + + int id = theIdCounter.fetchAndAddRelaxed(1); // generate unique ID + + if (name.isEmpty()) { + QHostInfo hostInfo(id); + hostInfo.setError(QHostInfo::HostNotFound); + hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given")); + QScopedPointer<QHostInfoResult> result(new QHostInfoResult); + QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)), + receiver, member, Qt::QueuedConnection); + result.data()->emitResultsReady(hostInfo); + return id; + } + +#ifndef Q_OS_SYMBIAN + QHostInfoLookupManager *manager = theHostInfoLookupManager(); + + if (manager) { + // the application is still alive + if (manager->cache.isEnabled()) { + // check cache first + bool valid = false; + QHostInfo info = manager->cache.get(name, &valid); + if (valid) { + info.setLookupId(id); + QHostInfoResult result; + QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection); + result.emitResultsReady(info); + return id; + } + } + + // cache is not enabled or it was not in the cache, do normal lookup + QHostInfoRunnable* runnable = new QHostInfoRunnable(name, id); + QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection); + manager->scheduleLookup(runnable); + } +#else + QSymbianHostInfoLookupManager *manager = theHostInfoLookupManager(); + + if (manager) { + // the application is still alive + if (manager->cache.isEnabled()) { + // check cache first + bool valid = false; + QHostInfo info = manager->cache.get(name, &valid); + if (valid) { + info.setLookupId(id); + QHostInfoResult result; + QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection); + result.emitResultsReady(info); + return id; + } + } + + // cache is not enabled or it was not in the cache, do normal lookup +#ifndef QT_NO_BEARERMANAGEMENT + QSharedPointer<QNetworkSession> networkSession; + QVariant v(receiver->property("_q_networksession")); + if (v.isValid()) + networkSession = qvariant_cast< QSharedPointer<QNetworkSession> >(v); +#endif + + QSymbianHostResolver *symbianResolver = 0; + QT_TRAP_THROWING(symbianResolver = new QSymbianHostResolver(name, id, networkSession)); + QObject::connect(&symbianResolver->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection); + manager->scheduleLookup(symbianResolver); + } +#endif + + return id; +} + +/*! + Aborts the host lookup with the ID \a id, as returned by lookupHost(). + + \sa lookupHost(), lookupId() +*/ +void QHostInfo::abortHostLookup(int id) +{ + theHostInfoLookupManager()->abortLookup(id); +} + +/*! + Looks up the IP address(es) for the given host \a name. The + function blocks during the lookup which means that execution of + the program is suspended until the results of the lookup are + ready. Returns the result of the lookup in a QHostInfo object. + + If you pass a literal IP address to \a name instead of a host name, + QHostInfo will search for the domain name for the IP (i.e., QHostInfo will + perform a \e reverse lookup). On success, the returned QHostInfo will + contain both the resolved domain name and IP addresses for the host name. + + \sa lookupHost() +*/ +QHostInfo QHostInfo::fromName(const QString &name) +{ +#if defined QHOSTINFO_DEBUG + qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData()); +#endif + + QHostInfo hostInfo = QHostInfoAgent::fromName(name); + QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + manager->cache.put(name, hostInfo); + return hostInfo; +} + +#ifndef QT_NO_BEARERMANAGEMENT +QHostInfo QHostInfoPrivate::fromName(const QString &name, QSharedPointer<QNetworkSession> session) +{ +#if defined QHOSTINFO_DEBUG + qDebug("QHostInfoPrivate::fromName(\"%s\") with session %p",name.toLatin1().constData(), session.data()); +#endif + + QHostInfo hostInfo = QHostInfoAgent::fromName(name, session); + QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + manager->cache.put(name, hostInfo); + return hostInfo; +} +#endif + +#ifndef Q_OS_SYMBIAN +// This function has a special implementation for symbian right now in qhostinfo_symbian.cpp but not on other OS. +QHostInfo QHostInfoAgent::fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession) +{ + return QHostInfoAgent::fromName(hostName); +} +#endif + + +/*! + \enum QHostInfo::HostInfoError + + This enum describes the various errors that can occur when trying + to resolve a host name. + + \value NoError The lookup was successful. + \value HostNotFound No IP addresses were found for the host. + \value UnknownError An unknown error occurred. + + \sa error(), setError() +*/ + +/*! + Constructs an empty host info object with lookup ID \a id. + + \sa lookupId() +*/ +QHostInfo::QHostInfo(int id) + : d(new QHostInfoPrivate) +{ + d->lookupId = id; +} + +/*! + Constructs a copy of \a other. +*/ +QHostInfo::QHostInfo(const QHostInfo &other) + : d(new QHostInfoPrivate(*other.d.data())) +{ +} + +/*! + Assigns the data of the \a other object to this host info object, + and returns a reference to it. +*/ +QHostInfo &QHostInfo::operator=(const QHostInfo &other) +{ + *d.data() = *other.d.data(); + return *this; +} + +/*! + Destroys the host info object. +*/ +QHostInfo::~QHostInfo() +{ +} + +/*! + Returns the list of IP addresses associated with hostName(). This + list may be empty. + + Example: + + \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 5 + + \sa hostName(), error() +*/ +QList<QHostAddress> QHostInfo::addresses() const +{ + return d->addrs; +} + +/*! + Sets the list of addresses in this QHostInfo to \a addresses. + + \sa addresses() +*/ +void QHostInfo::setAddresses(const QList<QHostAddress> &addresses) +{ + d->addrs = addresses; +} + +/*! + Returns the name of the host whose IP addresses were looked up. + + \sa localHostName() +*/ +QString QHostInfo::hostName() const +{ + return d->hostName; +} + +/*! + Sets the host name of this QHostInfo to \a hostName. + + \sa hostName() +*/ +void QHostInfo::setHostName(const QString &hostName) +{ + d->hostName = hostName; +} + +/*! + Returns the type of error that occurred if the host name lookup + failed; otherwise returns NoError. + + \sa setError(), errorString() +*/ +QHostInfo::HostInfoError QHostInfo::error() const +{ + return d->err; +} + +/*! + Sets the error type of this QHostInfo to \a error. + + \sa error(), errorString() +*/ +void QHostInfo::setError(HostInfoError error) +{ + d->err = error; +} + +/*! + Returns the ID of this lookup. + + \sa setLookupId(), abortHostLookup(), hostName() +*/ +int QHostInfo::lookupId() const +{ + return d->lookupId; +} + +/*! + Sets the ID of this lookup to \a id. + + \sa lookupId(), lookupHost() +*/ +void QHostInfo::setLookupId(int id) +{ + d->lookupId = id; +} + +/*! + If the lookup failed, this function returns a human readable + description of the error; otherwise "Unknown error" is returned. + + \sa setErrorString(), error() +*/ +QString QHostInfo::errorString() const +{ + return d->errorStr; +} + +/*! + Sets the human readable description of the error that occurred to \a str + if the lookup failed. + + \sa errorString(), setError() +*/ +void QHostInfo::setErrorString(const QString &str) +{ + d->errorStr = str; +} + +/*! + \fn QString QHostInfo::localHostName() + + Returns the host name of this machine. + + \sa hostName() +*/ + +/*! + \fn QString QHostInfo::localDomainName() + + Returns the DNS domain of this machine. + + Note: DNS domains are not related to domain names found in + Windows networks. + + \sa hostName() +*/ + +#ifndef Q_OS_SYMBIAN +QHostInfoRunnable::QHostInfoRunnable(QString hn, int i) : toBeLookedUp(hn), id(i) +{ + setAutoDelete(true); +} + +// the QHostInfoLookupManager will at some point call this via a QThreadPool +void QHostInfoRunnable::run() +{ + QHostInfoLookupManager *manager = theHostInfoLookupManager(); + // check aborted + if (manager->wasAborted(id)) { + manager->lookupFinished(this); + return; + } + + QHostInfo hostInfo; + + // QHostInfo::lookupHost already checks the cache. However we need to check + // it here too because it might have been cache saved by another QHostInfoRunnable + // in the meanwhile while this QHostInfoRunnable was scheduled but not running + if (manager->cache.isEnabled()) { + // check the cache first + bool valid = false; + hostInfo = manager->cache.get(toBeLookedUp, &valid); + if (!valid) { + // not in cache, we need to do the lookup and store the result in the cache + hostInfo = QHostInfoAgent::fromName(toBeLookedUp); + manager->cache.put(toBeLookedUp, hostInfo); + } + } else { + // cache is not enabled, just do the lookup and continue + hostInfo = QHostInfoAgent::fromName(toBeLookedUp); + } + + // check aborted again + if (manager->wasAborted(id)) { + manager->lookupFinished(this); + return; + } + + // signal emission + hostInfo.setLookupId(id); + resultEmitter.emitResultsReady(hostInfo); + + // now also iterate through the postponed ones + { + QMutexLocker locker(&manager->mutex); + QMutableListIterator<QHostInfoRunnable*> iterator(manager->postponedLookups); + while (iterator.hasNext()) { + QHostInfoRunnable* postponed = iterator.next(); + if (toBeLookedUp == postponed->toBeLookedUp) { + // we can now emit + iterator.remove(); + hostInfo.setLookupId(postponed->id); + postponed->resultEmitter.emitResultsReady(hostInfo); + } + } + } + + manager->lookupFinished(this); + + // thread goes back to QThreadPool +} + +QHostInfoLookupManager::QHostInfoLookupManager() : mutex(QMutex::Recursive), wasDeleted(false) +{ + moveToThread(QCoreApplicationPrivate::mainThread()); + connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection); + threadPool.setMaxThreadCount(5); // do 5 DNS lookups in parallel +} + +QHostInfoLookupManager::~QHostInfoLookupManager() +{ + wasDeleted = true; + + // don't qDeleteAll currentLookups, the QThreadPool has ownership + clear(); +} + +void QHostInfoLookupManager::clear() +{ + { + QMutexLocker locker(&mutex); + qDeleteAll(postponedLookups); + qDeleteAll(scheduledLookups); + qDeleteAll(finishedLookups); + postponedLookups.clear(); + scheduledLookups.clear(); + finishedLookups.clear(); + } + + threadPool.waitForDone(); + cache.clear(); +} + +void QHostInfoLookupManager::work() +{ + if (wasDeleted) + return; + + // goals of this function: + // - launch new lookups via the thread pool + // - make sure only one lookup per host/IP is in progress + + QMutexLocker locker(&mutex); + + if (!finishedLookups.isEmpty()) { + // remove ID from aborted if it is in there + for (int i = 0; i < finishedLookups.length(); i++) { + abortedLookups.removeAll(finishedLookups.at(i)->id); + } + + finishedLookups.clear(); + } + + if (!postponedLookups.isEmpty()) { + // try to start the postponed ones + + QMutableListIterator<QHostInfoRunnable*> iterator(postponedLookups); + while (iterator.hasNext()) { + QHostInfoRunnable* postponed = iterator.next(); + + // check if none of the postponed hostnames is currently running + bool alreadyRunning = false; + for (int i = 0; i < currentLookups.length(); i++) { + if (currentLookups.at(i)->toBeLookedUp == postponed->toBeLookedUp) { + alreadyRunning = true; + break; + } + } + if (!alreadyRunning) { + iterator.remove(); + scheduledLookups.prepend(postponed); // prepend! we want to finish it ASAP + } + } + } + + if (!scheduledLookups.isEmpty()) { + // try to start the new ones + QMutableListIterator<QHostInfoRunnable*> iterator(scheduledLookups); + while (iterator.hasNext()) { + QHostInfoRunnable *scheduled = iterator.next(); + + // check if a lookup for this host is already running, then postpone + for (int i = 0; i < currentLookups.size(); i++) { + if (currentLookups.at(i)->toBeLookedUp == scheduled->toBeLookedUp) { + iterator.remove(); + postponedLookups.append(scheduled); + scheduled = 0; + break; + } + } + + if (scheduled && currentLookups.size() < threadPool.maxThreadCount()) { + // runnable now running in new thread, track this in currentLookups + threadPool.start(scheduled); + iterator.remove(); + currentLookups.append(scheduled); + } else { + // was postponed, continue iterating + continue; + } + }; + } +} + +// called by QHostInfo +void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r) +{ + if (wasDeleted) + return; + + QMutexLocker locker(&this->mutex); + scheduledLookups.enqueue(r); + work(); +} + +// called by QHostInfo +void QHostInfoLookupManager::abortLookup(int id) +{ + if (wasDeleted) + return; + + QMutexLocker locker(&this->mutex); + + // is postponed? delete and return + for (int i = 0; i < postponedLookups.length(); i++) { + if (postponedLookups.at(i)->id == id) { + delete postponedLookups.takeAt(i); + return; + } + } + + // is scheduled? delete and return + for (int i = 0; i < scheduledLookups.length(); i++) { + if (scheduledLookups.at(i)->id == id) { + delete scheduledLookups.takeAt(i); + return; + } + } + + if (!abortedLookups.contains(id)) + abortedLookups.append(id); +} + +// called from QHostInfoRunnable +bool QHostInfoLookupManager::wasAborted(int id) +{ + if (wasDeleted) + return true; + + QMutexLocker locker(&this->mutex); + return abortedLookups.contains(id); +} + +// called from QHostInfoRunnable +void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r) +{ + if (wasDeleted) + return; + + QMutexLocker locker(&this->mutex); + currentLookups.removeOne(r); + finishedLookups.append(r); + work(); +} +#endif + +// This function returns immediately when we had a result in the cache, else it will later emit a signal +QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id) +{ + *valid = false; + *id = -1; + + // check cache + QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + if (manager && manager->cache.isEnabled()) { + QHostInfo info = manager->cache.get(name, valid); + if (*valid) { + return info; + } + } + + // was not in cache, trigger lookup + *id = QHostInfo::lookupHost(name, receiver, member); + + // return empty response, valid==false + return QHostInfo(); +} + +void qt_qhostinfo_clear_cache() +{ + QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + if (manager) { + manager->clear(); + } +} + +void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e) +{ + QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + if (manager) { + manager->cache.setEnabled(e); + } +} + +// cache for 60 seconds +// cache 64 items +QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(64) +{ +#ifdef QT_QHOSTINFO_CACHE_DISABLED_BY_DEFAULT + enabled = false; +#endif +} + +bool QHostInfoCache::isEnabled() +{ + return enabled; +} + +// this function is currently only used for the auto tests +// and not usable by public API +void QHostInfoCache::setEnabled(bool e) +{ + enabled = e; +} + + +QHostInfo QHostInfoCache::get(const QString &name, bool *valid) +{ + QMutexLocker locker(&this->mutex); + + *valid = false; + if (cache.contains(name)) { + QHostInfoCacheElement *element = cache.object(name); + if (element->age.elapsed() < max_age*1000) + *valid = true; + return element->info; + + // FIXME idea: + // if too old but not expired, trigger a new lookup + // to freshen our cache + } + + return QHostInfo(); +} + +void QHostInfoCache::put(const QString &name, const QHostInfo &info) +{ + // if the lookup failed, don't cache + if (info.error() != QHostInfo::NoError) + return; + + QHostInfoCacheElement* element = new QHostInfoCacheElement(); + element->info = info; + element->age = QElapsedTimer(); + element->age.start(); + + QMutexLocker locker(&this->mutex); + cache.insert(name, element); // cache will take ownership +} + +void QHostInfoCache::clear() +{ + QMutexLocker locker(&this->mutex); + cache.clear(); +} + +QAbstractHostInfoLookupManager* QAbstractHostInfoLookupManager::globalInstance() +{ + return theHostInfoLookupManager(); +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo.h b/src/network/kernel/qhostinfo.h new file mode 100644 index 0000000000..33453e76ce --- /dev/null +++ b/src/network/kernel/qhostinfo.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 QHOSTINFO_H +#define QHOSTINFO_H + +#include <QtCore/qlist.h> +#include <QtCore/qscopedpointer.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QObject; +class QHostInfoPrivate; + +class Q_NETWORK_EXPORT QHostInfo +{ +public: + enum HostInfoError { + NoError, + HostNotFound, + UnknownError + }; + + QHostInfo(int lookupId = -1); + QHostInfo(const QHostInfo &d); + QHostInfo &operator=(const QHostInfo &d); + ~QHostInfo(); + + QString hostName() const; + void setHostName(const QString &name); + + QList<QHostAddress> addresses() const; + void setAddresses(const QList<QHostAddress> &addresses); + + HostInfoError error() const; + void setError(HostInfoError error); + + QString errorString() const; + void setErrorString(const QString &errorString); + + void setLookupId(int id); + int lookupId() const; + + static int lookupHost(const QString &name, QObject *receiver, const char *member); + static void abortHostLookup(int lookupId); + + static QHostInfo fromName(const QString &name); + static QString localHostName(); + static QString localDomainName(); + +private: + QScopedPointer<QHostInfoPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QHOSTINFO_H diff --git a/src/network/kernel/qhostinfo_p.h b/src/network/kernel/qhostinfo_p.h new file mode 100644 index 0000000000..8da069262a --- /dev/null +++ b/src/network/kernel/qhostinfo_p.h @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** 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 QHOSTINFO_P_H +#define QHOSTINFO_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 QHostInfo class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qcoreapplication.h" +#include "private/qcoreapplication_p.h" +#include "QtNetwork/qhostinfo.h" +#include "QtCore/qmutex.h" +#include "QtCore/qwaitcondition.h" +#include "QtCore/qobject.h" +#include "QtCore/qpointer.h" +#include "QtCore/qthread.h" +#include "QtCore/qthreadpool.h" +#include "QtCore/qmutex.h" +#include "QtCore/qrunnable.h" +#include "QtCore/qlist.h" +#include "QtCore/qqueue.h" +#include <QElapsedTimer> +#include <QCache> + +#include <QNetworkSession> +#include <QSharedPointer> + +#ifdef Q_OS_SYMBIAN +// Symbian Headers +#include <es_sock.h> +#include <in_sock.h> +#endif + + +QT_BEGIN_NAMESPACE + + +class QHostInfoResult : public QObject +{ + Q_OBJECT +public Q_SLOTS: + inline void emitResultsReady(const QHostInfo &info) + { + emit resultsReady(info); + } + +Q_SIGNALS: + void resultsReady(const QHostInfo &info); +}; + +// needs to be QObject because fromName calls tr() +class QHostInfoAgent : public QObject +{ + Q_OBJECT +public: + static QHostInfo fromName(const QString &hostName); + static QHostInfo fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession); + +#ifdef Q_OS_SYMBIAN + static int lookupHost(const QString &name, QObject *receiver, const char *member); + static void abortHostLookup(int lookupId); +#endif +}; + +class QHostInfoPrivate +{ +public: + inline QHostInfoPrivate() + : err(QHostInfo::NoError), + errorStr(QLatin1String(QT_TRANSLATE_NOOP("QHostInfo", "Unknown error"))), + lookupId(0) + { + } +#ifndef QT_NO_BEARERMANAGEMENT + //not a public API yet + static QHostInfo fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession); +#endif + + QHostInfo::HostInfoError err; + QString errorStr; + QList<QHostAddress> addrs; + QString hostName; + int lookupId; +}; + +// These functions are outside of the QHostInfo class and strictly internal. +// Do NOT use them outside of QAbstractSocket. +QHostInfo Q_NETWORK_EXPORT qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id); +void Q_AUTOTEST_EXPORT qt_qhostinfo_clear_cache(); +void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e); + +class QHostInfoCache +{ +public: + QHostInfoCache(); + const int max_age; // seconds + + QHostInfo get(const QString &name, bool *valid); + void put(const QString &name, const QHostInfo &info); + void clear(); + + bool isEnabled(); + void setEnabled(bool e); +private: + bool enabled; + struct QHostInfoCacheElement { + QHostInfo info; + QElapsedTimer age; + }; + QCache<QString,QHostInfoCacheElement> cache; + QMutex mutex; +}; + +// the following classes are used for the (normal) case: We use multiple threads to lookup DNS + +class QHostInfoRunnable : public QRunnable +{ +public: + QHostInfoRunnable (QString hn, int i); + void run(); + + QString toBeLookedUp; + int id; + QHostInfoResult resultEmitter; +}; + + +class QAbstractHostInfoLookupManager : public QObject +{ + Q_OBJECT + +public: + ~QAbstractHostInfoLookupManager() {} + virtual void clear() = 0; + + QHostInfoCache cache; + +protected: + QAbstractHostInfoLookupManager() {} + static QAbstractHostInfoLookupManager* globalInstance(); + +}; + +#ifndef Q_OS_SYMBIAN +class QHostInfoLookupManager : public QAbstractHostInfoLookupManager +{ + Q_OBJECT +public: + QHostInfoLookupManager(); + ~QHostInfoLookupManager(); + + void clear(); + void work(); + + // called from QHostInfo + void scheduleLookup(QHostInfoRunnable *r); + void abortLookup(int id); + + // called from QHostInfoRunnable + void lookupFinished(QHostInfoRunnable *r); + bool wasAborted(int id); + + friend class QHostInfoRunnable; +protected: + QList<QHostInfoRunnable*> currentLookups; // in progress + QList<QHostInfoRunnable*> postponedLookups; // postponed because in progress for same host + QQueue<QHostInfoRunnable*> scheduledLookups; // not yet started + QList<QHostInfoRunnable*> finishedLookups; // recently finished + QList<int> abortedLookups; // ids of aborted lookups + + QThreadPool threadPool; + + QMutex mutex; + + bool wasDeleted; + +private slots: + void waitForThreadPoolDone() { threadPool.waitForDone(); } +}; + +#else + +class QSymbianHostResolver : public CActive +{ +public: + QSymbianHostResolver(const QString &hostName, int id, QSharedPointer<QNetworkSession> networkSession); + ~QSymbianHostResolver(); + + void requestHostLookup(); + void abortHostLookup(); + int id(); + + void returnResults(); + + QHostInfoResult resultEmitter; + +private: + void DoCancel(); + void RunL(); + void run(); + TInt RunError(TInt aError); + + void processNameResult(); + void nextNameResult(); + void processAddressResult(); + +private: + int iId; + + const QString iHostName; + QString iEncodedHostName; + TPtrC iHostNamePtr; + + RSocketServ& iSocketServ; + RHostResolver iHostResolver; + QSharedPointer<QNetworkSession> iNetworkSession; + + TNameEntry iNameResult; + TInetAddr IpAdd; + + QHostAddress iAddress; + + QHostInfo iResults; + + QList<QHostAddress> iHostAddresses; + + enum { + EIdle, + EGetByName, + EGetByAddress, + ECompleteFromCache, + EError + } iState; +}; + +class QSymbianHostInfoLookupManager : public QAbstractHostInfoLookupManager +{ + Q_OBJECT +public: + QSymbianHostInfoLookupManager(); + ~QSymbianHostInfoLookupManager(); + + static QSymbianHostInfoLookupManager* globalInstance(); + + int id(); + void clear(); + + // called from QHostInfo + void scheduleLookup(QSymbianHostResolver *r); + void abortLookup(int id); + + // called from QSymbianHostResolver + void lookupFinished(QSymbianHostResolver *r); + +private: + void runNextLookup(); + + // this is true for single threaded use, with multiple threads the max is ((number of threads) + KMaxConcurrentLookups - 1) + static const int KMaxConcurrentLookups = 5; + + QList<QSymbianHostResolver*> iCurrentLookups; + QList<QSymbianHostResolver*> iScheduledLookups; + + QMutex mutex; +}; +#endif + + + +QT_END_NAMESPACE + +#endif // QHOSTINFO_P_H diff --git a/src/network/kernel/qhostinfo_symbian.cpp b/src/network/kernel/qhostinfo_symbian.cpp new file mode 100644 index 0000000000..2a8de1d2de --- /dev/null +++ b/src/network/kernel/qhostinfo_symbian.cpp @@ -0,0 +1,600 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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 QHOSTINFO_DEBUG + +// Qt Headers +#include <QByteArray> +#include <QUrl> +#include <QList> + +#include "qplatformdefs.h" + +#include "qhostinfo_p.h" +#include <private/qcore_symbian_p.h> +#include <private/qsystemerror_p.h> +#include <private/qnetworksession_p.h> + +// Header does not exist in the S60 5.0 SDK +//#include <networking/dnd_err.h> +const TInt KErrDndNameNotFound = -5120; // Returned when no data found for GetByName +const TInt KErrDndAddrNotFound = -5121; // Returned when no data found for GetByAddr + +QT_BEGIN_NAMESPACE + +static void setError_helper(QHostInfo &info, TInt symbianError) +{ + switch (symbianError) { + case KErrDndNameNotFound: + case KErrDndAddrNotFound: + case KErrNotFound: + case KErrEof: + // various "no more results" error codes + info.setError(QHostInfo::HostNotFound); + info.setErrorString(QObject::tr("Host not found")); + break; + default: + // Unknown error + info.setError(QHostInfo::UnknownError); + info.setErrorString(QSystemError(symbianError, QSystemError::NativeError).toString()); + break; + } +} + +QHostInfo QHostInfoAgent::fromName(const QString &hostName, QSharedPointer<QNetworkSession> networkSession) +{ + QHostInfo results; + + // Connect to ESOCK + RSocketServ socketServ(qt_symbianGetSocketServer()); + RHostResolver hostResolver; + + + int err; + if (networkSession) + err = QNetworkSessionPrivate::nativeOpenHostResolver(*networkSession, hostResolver, KAfInet, KProtocolInetUdp); + else + err = hostResolver.Open(socketServ, KAfInet, KProtocolInetUdp); + if (err) { + setError_helper(results, err); + return results; + } + + TNameEntry nameResult; + +#if defined(QHOSTINFO_DEBUG) + qDebug("QHostInfoAgent::fromName(%s) looking up...", + hostName.toLatin1().constData()); +#endif + + QHostAddress address; + if (address.setAddress(hostName)) { + // Reverse lookup +#if defined(QHOSTINFO_DEBUG) + qDebug("(reverse lookup)"); +#endif + TInetAddr IpAdd; + IpAdd.Input(qt_QString2TPtrC(hostName)); + + // Synchronous request. nameResult returns Host Name. + err = hostResolver.GetByAddress(IpAdd, nameResult); + if (err) { + //for behavioural compatibility with Qt 4.7 and unix/windows + //backends: don't report error, return ip address as host name + results.setHostName(address.toString()); + } else { + results.setHostName(qt_TDesC2QString(nameResult().iName)); + } + results.setAddresses(QList<QHostAddress>() << address); + return results; + } + + // IDN support + QByteArray aceHostname = QUrl::toAce(hostName); + results.setHostName(hostName); + if (aceHostname.isEmpty()) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(hostName.isEmpty() ? + QCoreApplication::translate("QHostInfoAgent", "No host name given") : + QCoreApplication::translate("QHostInfoAgent", "Invalid hostname")); + return results; + } + + + // Call RHostResolver::GetByAddress, and place all IPv4 addresses at the start and + // the IPv6 addresses at the end of the address list in results. + + // Synchronous request. + err = hostResolver.GetByName(qt_QString2TPtrC(QString::fromLatin1(aceHostname)), nameResult); + if (err) { + setError_helper(results, err); + return results; + } + + QList<QHostAddress> hostAddresses; + + TInetAddr hostAdd = nameResult().iAddr; + // 39 is the maximum length of an IPv6 address. + TBuf<39> ipAddr; + + // Fill ipAddr with the IP address from hostAdd + hostAdd.Output(ipAddr); + if (ipAddr.Length() > 0) + hostAddresses.append(QHostAddress(qt_TDesC2QString(ipAddr))); + + // Check if there's more than one IP address linkd to this name + while (hostResolver.Next(nameResult) == KErrNone) { + hostAdd = nameResult().iAddr; + hostAdd.Output(ipAddr); + + // Ensure that record is valid (not an alias and with length greater than 0) + if (!(nameResult().iFlags & TNameRecord::EAlias) && !(hostAdd.IsUnspecified())) { + hostAddresses.append(QHostAddress(qt_TDesC2QString(ipAddr))); + } + } + + hostResolver.Close(); + + results.setAddresses(hostAddresses); + return results; +} + +QHostInfo QHostInfoAgent::fromName(const QString &hostName) +{ + // null shared pointer + QSharedPointer<QNetworkSession> networkSession; + return fromName(hostName, networkSession); +} + +QString QHostInfo::localHostName() +{ + // Connect to ESOCK + RSocketServ socketServ(qt_symbianGetSocketServer()); + RHostResolver hostResolver; + + // RConnection not required to get the host name + int err = hostResolver.Open(socketServ, KAfInet, KProtocolInetUdp); + if (err) + return QString(); + + THostName hostName; + err = hostResolver.GetHostName(hostName); + if (err) + return QString(); + + hostResolver.Close(); + + return qt_TDesC2QString(hostName); +} + +QString QHostInfo::localDomainName() +{ + // This concept does not exist on Symbian OS because the device can be on + // multiple networks with multiple "local domain" names. + // For now, return a null string. + return QString(); +} + + +QSymbianHostResolver::QSymbianHostResolver(const QString &hostName, int identifier, QSharedPointer<QNetworkSession> networkSession) + : CActive(CActive::EPriorityStandard), iHostName(hostName), + iSocketServ(qt_symbianGetSocketServer()), iNetworkSession(networkSession), iResults(identifier) +{ + CActiveScheduler::Add(this); +} + +QSymbianHostResolver::~QSymbianHostResolver() +{ +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::~QSymbianHostResolver" << id(); +#endif + Cancel(); + iHostResolver.Close(); +} + +// Async equivalent to QHostInfoAgent::fromName() +void QSymbianHostResolver::requestHostLookup() +{ + +#if defined(QHOSTINFO_DEBUG) + qDebug("QSymbianHostResolver::requestHostLookup(%s) looking up... (id = %d)", + iHostName.toLatin1().constData(), id()); +#endif + + QSymbianHostInfoLookupManager *manager = QSymbianHostInfoLookupManager::globalInstance(); + if (manager->cache.isEnabled()) { + //check if name has been put in the cache while this request was queued + bool valid; + QHostInfo cachedResult = manager->cache.get(iHostName, &valid); + if (valid) { +#if defined(QHOSTINFO_DEBUG) + qDebug("...found in cache"); +#endif + iResults = cachedResult; + iState = ECompleteFromCache; + SetActive(); + TRequestStatus* stat = &iStatus; + User::RequestComplete(stat, KErrNone); + return; + } + } + + int err; + if (iNetworkSession) { + err = QNetworkSessionPrivate::nativeOpenHostResolver(*iNetworkSession, iHostResolver, KAfInet, KProtocolInetUdp); +#if defined(QHOSTINFO_DEBUG) + qDebug("using resolver from session (err = %d)", err); +#endif + } else { + err = iHostResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp); +#if defined(QHOSTINFO_DEBUG) + qDebug("using default resolver (err = %d)", err); +#endif + } + if (err) { + setError_helper(iResults, err); + } else { + + if (iAddress.setAddress(iHostName)) { + // Reverse lookup + IpAdd.Input(qt_QString2TPtrC(iHostName)); + + // Asynchronous request. + iHostResolver.GetByAddress(IpAdd, iNameResult, iStatus); // <---- ASYNC + iState = EGetByAddress; + + } else { + + // IDN support + QByteArray aceHostname = QUrl::toAce(iHostName); + iResults.setHostName(iHostName); + if (aceHostname.isEmpty()) { + iResults.setError(QHostInfo::HostNotFound); + iResults.setErrorString(iHostName.isEmpty() ? + QCoreApplication::translate("QHostInfoAgent", "No host name given") : + QCoreApplication::translate("QHostInfoAgent", "Invalid hostname")); + + err = KErrArgument; + } else { + iEncodedHostName = QString::fromLatin1(aceHostname); + iHostNamePtr.Set(qt_QString2TPtrC(iEncodedHostName)); + + // Asynchronous request. + iHostResolver.GetByName(iHostNamePtr, iNameResult, iStatus); + iState = EGetByName; + } + } + } + SetActive(); + if (err) { + iHostResolver.Close(); + + //self complete so that RunL can inform manager without causing recursion + iState = EError; + TRequestStatus* stat = &iStatus; + User::RequestComplete(stat, err); + } +} + +void QSymbianHostResolver::abortHostLookup() +{ + if (resultEmitter.thread() == QThread::currentThread()) { +#ifdef QHOSTINFO_DEBUG + qDebug("QSymbianHostResolver::abortHostLookup - deleting %d", id()); +#endif + //normal case, abort from same thread it was started + delete this; //will cancel outstanding request + } else { +#ifdef QHOSTINFO_DEBUG + qDebug("QSymbianHostResolver::abortHostLookup - detaching %d", id()); +#endif + //abort from different thread, carry on but don't report the results + resultEmitter.disconnect(); + } +} + +void QSymbianHostResolver::DoCancel() +{ +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostResolver::DoCancel" << QThread::currentThreadId() << id() << (int)iState << this; +#endif + if (iState == EGetByAddress || iState == EGetByName) { + //these states have made an async request to host resolver + iHostResolver.Cancel(); + } else { + //for the self completing states there is nothing to cancel + Q_ASSERT(iState == EError || iState == ECompleteFromCache); + } +} + +void QSymbianHostResolver::RunL() +{ + QT_TRYCATCH_LEAVING(run()); +} + +void QSymbianHostResolver::run() +{ + switch (iState) { + case EGetByName: + processNameResult(); + break; + case EGetByAddress: + processAddressResult(); + break; + case ECompleteFromCache: + case EError: + returnResults(); + break; + default: + qWarning("QSymbianHostResolver internal error, bad state in run()"); + iResults.setError(QHostInfo::UnknownError); + iResults.setErrorString(QSystemError(KErrCorrupt,QSystemError::NativeError).toString()); + returnResults(); + } +} + +void QSymbianHostResolver::returnResults() +{ +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostResolver::returnResults" << iResults.error() << iResults.errorString(); + foreach (QHostAddress addr, iResults.addresses()) + qDebug() << addr; +#endif + iState = EIdle; + + QSymbianHostInfoLookupManager *manager = QSymbianHostInfoLookupManager::globalInstance(); + if (manager->cache.isEnabled()) { + manager->cache.put(iHostName, iResults); + } + manager->lookupFinished(this); + + resultEmitter.emitResultsReady(iResults); + + delete this; +} + +TInt QSymbianHostResolver::RunError(TInt aError) +{ + QT_TRY { + iState = EIdle; + + QSymbianHostInfoLookupManager *manager = QSymbianHostInfoLookupManager::globalInstance(); + manager->lookupFinished(this); + + setError_helper(iResults, aError); + + resultEmitter.emitResultsReady(iResults); + } + QT_CATCH(...) {} + + delete this; + + return KErrNone; +} + +void QSymbianHostResolver::processNameResult() +{ + if (iStatus.Int() == KErrNone) { + TInetAddr hostAdd = iNameResult().iAddr; + // 39 is the maximum length of an IPv6 address. + TBuf<39> ipAddr; + + hostAdd.Output(ipAddr); + + // Ensure that record is valid (not an alias and with length greater than 0) + if (!(iNameResult().iFlags & TNameRecord::EAlias) && !(hostAdd.IsUnspecified())) { + iHostAddresses.append(QHostAddress(qt_TDesC2QString(ipAddr))); + } + + iState = EGetByName; + iHostResolver.Next(iNameResult, iStatus); + SetActive(); + } + else { + // No more addresses, so return the results (or an error if there aren't any). +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostResolver::processNameResult with err=" << iStatus.Int() << "count=" << iHostAddresses.count(); +#endif + if (iHostAddresses.count() > 0) { + iResults.setAddresses(iHostAddresses); + } else { + iState = EError; + setError_helper(iResults, iStatus.Int()); + } + returnResults(); + } +} + +void QSymbianHostResolver::processAddressResult() +{ + TInt err = iStatus.Int(); + + if (err < 0) { + //For behavioural compatibility with Qt 4.7, don't report errors on reverse lookup, + //return the address as a string (same as unix/windows backends) + iResults.setHostName(iAddress.toString()); + } else { + iResults.setHostName(qt_TDesC2QString(iNameResult().iName)); + } + iResults.setAddresses(QList<QHostAddress>() << iAddress); + returnResults(); +} + + +int QSymbianHostResolver::id() +{ + return iResults.lookupId(); +} + +QSymbianHostInfoLookupManager::QSymbianHostInfoLookupManager() +{ +} + +QSymbianHostInfoLookupManager::~QSymbianHostInfoLookupManager() +{ +} + +void QSymbianHostInfoLookupManager::clear() +{ + QMutexLocker locker(&mutex); +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::clear" << QThread::currentThreadId(); +#endif + foreach (QSymbianHostResolver *hr, iCurrentLookups) + hr->abortHostLookup(); + iCurrentLookups.clear(); + qDeleteAll(iScheduledLookups); + cache.clear(); +} + +void QSymbianHostInfoLookupManager::lookupFinished(QSymbianHostResolver *r) +{ + QMutexLocker locker(&mutex); + +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::lookupFinished" << QThread::currentThreadId() << r->id() << "current" << iCurrentLookups.count() << "queued" << iScheduledLookups.count(); +#endif + // remove finished lookup from array and destroy + TInt count = iCurrentLookups.count(); + for (TInt i = 0; i < count; i++) { + if (iCurrentLookups[i]->id() == r->id()) { + iCurrentLookups.removeAt(i); + break; + } + } + + runNextLookup(); +} + +void QSymbianHostInfoLookupManager::runNextLookup() +{ +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::runNextLookup" << QThread::currentThreadId() << "current" << iCurrentLookups.count() << "queued" << iScheduledLookups.count(); +#endif + // check to see if there are any scheduled lookups + for (int i=0; i<iScheduledLookups.count(); i++) { + QSymbianHostResolver* hostResolver = iScheduledLookups.at(i); + if (hostResolver->resultEmitter.thread() == QThread::currentThread()) { + // if so, move one to the current lookups and run it + iCurrentLookups.append(hostResolver); + iScheduledLookups.removeAt(i); + hostResolver->requestHostLookup(); + // if spare capacity, try to start another one + if (iCurrentLookups.count() >= KMaxConcurrentLookups) + break; + i--; //compensate for removeAt + } + } +} + +// called from QHostInfo +void QSymbianHostInfoLookupManager::scheduleLookup(QSymbianHostResolver* r) +{ + QMutexLocker locker(&mutex); + +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::scheduleLookup" << QThread::currentThreadId() << r->id() << "current" << iCurrentLookups.count() << "queued" << iScheduledLookups.count(); +#endif + // Check to see if we have space on the current lookups pool. + bool defer = false; + if (iCurrentLookups.count() >= KMaxConcurrentLookups) { + // busy, defer unless there are no request in this thread + // at least one active request per thread with queued requests is needed + for (int i=0; i < iCurrentLookups.count();i++) { + if (iCurrentLookups.at(i)->resultEmitter.thread() == QThread::currentThread()) { + defer = true; + break; + } + } + } + if (defer) { + // If no, schedule for later. + iScheduledLookups.append(r); +#if defined(QHOSTINFO_DEBUG) + qDebug(" - scheduled"); +#endif + return; + } else { + // If yes, add it to the current lookups. + iCurrentLookups.append(r); + + // ... and trigger the async call. + r->requestHostLookup(); + } +} + +void QSymbianHostInfoLookupManager::abortLookup(int id) +{ + QMutexLocker locker(&mutex); + +#if defined(QHOSTINFO_DEBUG) + qDebug() << "QSymbianHostInfoLookupManager::abortLookup" << QThread::currentThreadId() << id << "current" << iCurrentLookups.count() << "queued" << iScheduledLookups.count(); +#endif + int i = 0; + // Find the aborted lookup by ID. + // First in the current lookups. + for (i = 0; i < iCurrentLookups.count(); i++) { + if (id == iCurrentLookups[i]->id()) { + QSymbianHostResolver* r = iCurrentLookups.at(i); + iCurrentLookups.removeAt(i); + r->abortHostLookup(); + runNextLookup(); + return; + } + } + // Then in the scheduled lookups. + for (i = 0; i < iScheduledLookups.count(); i++) { + if (id == iScheduledLookups[i]->id()) { + QSymbianHostResolver* r = iScheduledLookups.at(i); + iScheduledLookups.removeAt(i); + delete r; + return; + } + } +} + +QSymbianHostInfoLookupManager* QSymbianHostInfoLookupManager::globalInstance() +{ + return static_cast<QSymbianHostInfoLookupManager*> + (QAbstractHostInfoLookupManager::globalInstance()); +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp new file mode 100644 index 0000000000..8fc6bf6504 --- /dev/null +++ b/src/network/kernel/qhostinfo_unix.cpp @@ -0,0 +1,396 @@ +/**************************************************************************** +** +** 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 QHOSTINFO_DEBUG + +#include "qplatformdefs.h" + +#include "qhostinfo_p.h" +#include "private/qnativesocketengine_p.h" +#include "qiodevice.h" +#include <qbytearray.h> +#include <qlibrary.h> +#include <qurl.h> +#include <qfile.h> +#include <private/qmutexpool_p.h> +#include <private/qnet_unix_p.h> + +#include <sys/types.h> +#include <netdb.h> +#include <arpa/inet.h> +#if defined(Q_OS_VXWORKS) +# include <hostLib.h> +#else +# include <resolv.h> +#endif + +#if defined (QT_NO_GETADDRINFO) +#include <qmutex.h> +QT_BEGIN_NAMESPACE +Q_GLOBAL_STATIC(QMutex, getHostByNameMutex) +QT_END_NAMESPACE +#endif + +QT_BEGIN_NAMESPACE + +// Almost always the same. If not, specify in qplatformdefs.h. +#if !defined(QT_SOCKOPTLEN_T) +# define QT_SOCKOPTLEN_T QT_SOCKLEN_T +#endif + +// HP-UXi has a bug in getaddrinfo(3) that makes it thread-unsafe +// with this flag. So disable it in that platform. +#if defined(AI_ADDRCONFIG) && !defined(Q_OS_HPUX) +# define Q_ADDRCONFIG AI_ADDRCONFIG +#endif + +typedef struct __res_state *res_state_ptr; + +typedef int (*res_init_proto)(void); +static res_init_proto local_res_init = 0; +typedef int (*res_ninit_proto)(res_state_ptr); +static res_ninit_proto local_res_ninit = 0; +typedef void (*res_nclose_proto)(res_state_ptr); +static res_nclose_proto local_res_nclose = 0; +static res_state_ptr local_res = 0; + +static void resolveLibrary() +{ +#ifndef QT_NO_LIBRARY + QLibrary lib(QLatin1String("resolv")); + if (!lib.load()) + return; + + local_res_init = res_init_proto(lib.resolve("__res_init")); + if (!local_res_init) + local_res_init = res_init_proto(lib.resolve("res_init")); + + local_res_ninit = res_ninit_proto(lib.resolve("__res_ninit")); + if (!local_res_ninit) + local_res_ninit = res_ninit_proto(lib.resolve("res_ninit")); + + if (!local_res_ninit) { + // if we can't get a thread-safe context, we have to use the global _res state + local_res = res_state_ptr(lib.resolve("_res")); + } else { + local_res_nclose = res_nclose_proto(lib.resolve("res_nclose")); + if (!local_res_nclose) + local_res_nclose = res_nclose_proto(lib.resolve("__res_nclose")); + if (!local_res_nclose) + local_res_ninit = 0; + } +#endif +} + +QHostInfo QHostInfoAgent::fromName(const QString &hostName) +{ + QHostInfo results; + +#if defined(QHOSTINFO_DEBUG) + qDebug("QHostInfoAgent::fromName(%s) looking up...", + hostName.toLatin1().constData()); +#endif + + // Load res_init on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(QMutexPool::globalInstanceGet(&local_res_init)); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + // If res_init is available, poll it. + if (local_res_init) + local_res_init(); + + QHostAddress address; + if (address.setAddress(hostName)) { + // Reverse lookup +// Reverse lookups using getnameinfo are broken on darwin, use gethostbyaddr instead. +#if !defined (QT_NO_GETADDRINFO) && !defined (Q_OS_DARWIN) + sockaddr_in sa4; +#ifndef QT_NO_IPV6 + sockaddr_in6 sa6; +#endif + sockaddr *sa = 0; + QT_SOCKLEN_T saSize = 0; + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + sa = (sockaddr *)&sa4; + saSize = sizeof(sa4); + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + sa4.sin_addr.s_addr = htonl(address.toIPv4Address()); + } +#ifndef QT_NO_IPV6 + else { + sa = (sockaddr *)&sa6; + saSize = sizeof(sa6); + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + memcpy(sa6.sin6_addr.s6_addr, address.toIPv6Address().c, sizeof(sa6.sin6_addr.s6_addr)); + } +#endif + + char hbuf[NI_MAXHOST]; + if (sa && getnameinfo(sa, saSize, hbuf, sizeof(hbuf), 0, 0, 0) == 0) + results.setHostName(QString::fromLatin1(hbuf)); +#else + in_addr_t inetaddr = qt_safe_inet_addr(hostName.toLatin1().constData()); + struct hostent *ent = gethostbyaddr((const char *)&inetaddr, sizeof(inetaddr), AF_INET); + if (ent) + results.setHostName(QString::fromLatin1(ent->h_name)); +#endif + + if (results.hostName().isEmpty()) + results.setHostName(address.toString()); + results.setAddresses(QList<QHostAddress>() << address); + return results; + } + + // IDN support + QByteArray aceHostname = QUrl::toAce(hostName); + results.setHostName(hostName); + if (aceHostname.isEmpty()) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(hostName.isEmpty() ? + QCoreApplication::translate("QHostInfoAgent", "No host name given") : + QCoreApplication::translate("QHostInfoAgent", "Invalid hostname")); + return results; + } + +#if !defined (QT_NO_GETADDRINFO) + // Call getaddrinfo, and place all IPv4 addresses at the start and + // the IPv6 addresses at the end of the address list in results. + addrinfo *res = 0; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; +#ifdef Q_ADDRCONFIG + hints.ai_flags = Q_ADDRCONFIG; +#endif + + int result = getaddrinfo(aceHostname.constData(), 0, &hints, &res); +# ifdef Q_ADDRCONFIG + if (result == EAI_BADFLAGS) { + // if the lookup failed with AI_ADDRCONFIG set, try again without it + hints.ai_flags = 0; + result = getaddrinfo(aceHostname.constData(), 0, &hints, &res); + } +# endif + + if (result == 0) { + addrinfo *node = res; + QList<QHostAddress> addresses; + while (node) { +#ifdef QHOSTINFO_DEBUG + qDebug() << "getaddrinfo node: flags:" << node->ai_flags << "family:" << node->ai_family << "ai_socktype:" << node->ai_socktype << "ai_protocol:" << node->ai_protocol << "ai_addrlen:" << node->ai_addrlen; +#endif + if (node->ai_family == AF_INET) { + QHostAddress addr; + addr.setAddress(ntohl(((sockaddr_in *) node->ai_addr)->sin_addr.s_addr)); + if (!addresses.contains(addr)) + addresses.append(addr); + } +#ifndef QT_NO_IPV6 + else if (node->ai_family == AF_INET6) { + QHostAddress addr; + sockaddr_in6 *sa6 = (sockaddr_in6 *) node->ai_addr; + addr.setAddress(sa6->sin6_addr.s6_addr); + if (sa6->sin6_scope_id) + addr.setScopeId(QString::number(sa6->sin6_scope_id)); + if (!addresses.contains(addr)) + addresses.append(addr); + } +#endif + node = node->ai_next; + } + if (addresses.isEmpty() && node == 0) { + // Reached the end of the list, but no addresses were found; this + // means the list contains one or more unknown address types. + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown address type")); + } + + results.setAddresses(addresses); + freeaddrinfo(res); + } else if (result == EAI_NONAME + || result == EAI_FAIL +#ifdef EAI_NODATA + // EAI_NODATA is deprecated in RFC 3493 + || result == EAI_NODATA +#endif + ) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(tr("Host not found")); + } else { + results.setError(QHostInfo::UnknownError); + results.setErrorString(QString::fromLocal8Bit(gai_strerror(result))); + } + +#else + // Fall back to gethostbyname for platforms that don't define + // getaddrinfo. gethostbyname does not support IPv6, and it's not + // reentrant on all platforms. For now this is okay since we only + // use one QHostInfoAgent, but if more agents are introduced, locking + // must be provided. + QMutexLocker locker(::getHostByNameMutex()); + hostent *result = gethostbyname(aceHostname.constData()); + if (result) { + if (result->h_addrtype == AF_INET) { + QList<QHostAddress> addresses; + for (char **p = result->h_addr_list; *p != 0; p++) { + QHostAddress addr; + addr.setAddress(ntohl(*((quint32 *)*p))); + if (!addresses.contains(addr)) + addresses.prepend(addr); + } + results.setAddresses(addresses); + } else { + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown address type")); + } +#if !defined(Q_OS_VXWORKS) + } else if (h_errno == HOST_NOT_FOUND || h_errno == NO_DATA + || h_errno == NO_ADDRESS) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(tr("Host not found")); +#endif + } else { + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown error")); + } +#endif // !defined (QT_NO_GETADDRINFO) + +#if defined(QHOSTINFO_DEBUG) + if (results.error() != QHostInfo::NoError) { + qDebug("QHostInfoAgent::fromName(): error #%d %s", + h_errno, results.errorString().toLatin1().constData()); + } else { + QString tmp; + QList<QHostAddress> addresses = results.addresses(); + for (int i = 0; i < addresses.count(); ++i) { + if (i != 0) tmp += ", "; + tmp += addresses.at(i).toString(); + } + qDebug("QHostInfoAgent::fromName(): found %i entries for \"%s\": {%s}", + addresses.count(), hostName.toLatin1().constData(), + tmp.toLatin1().constData()); + } +#endif + return results; +} + +QString QHostInfo::localHostName() +{ + char hostName[512]; + if (gethostname(hostName, sizeof(hostName)) == -1) + return QString(); + hostName[sizeof(hostName) - 1] = '\0'; + return QString::fromLocal8Bit(hostName); +} + +QString QHostInfo::localDomainName() +{ +#if !defined(Q_OS_VXWORKS) + resolveLibrary(); + if (local_res_ninit) { + // using thread-safe version + res_state_ptr state = res_state_ptr(qMalloc(sizeof(*state))); + Q_CHECK_PTR(state); + memset(state, 0, sizeof(*state)); + local_res_ninit(state); + QString domainName = QUrl::fromAce(state->defdname); + if (domainName.isEmpty()) + domainName = QUrl::fromAce(state->dnsrch[0]); + local_res_nclose(state); + qFree(state); + + return domainName; + } + + if (local_res_init && local_res) { + // using thread-unsafe version + +#if defined(QT_NO_GETADDRINFO) + // We have to call res_init to be sure that _res was initialized + // So, for systems without getaddrinfo (which is thread-safe), we lock the mutex too + QMutexLocker locker(::getHostByNameMutex()); +#endif + local_res_init(); + QString domainName = QUrl::fromAce(local_res->defdname); + if (domainName.isEmpty()) + domainName = QUrl::fromAce(local_res->dnsrch[0]); + return domainName; + } +#endif + // nothing worked, try doing it by ourselves: + QFile resolvconf; +#if defined(_PATH_RESCONF) + resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF)); +#else + resolvconf.setFileName(QLatin1String("/etc/resolv.conf")); +#endif + if (!resolvconf.open(QIODevice::ReadOnly)) + return QString(); // failure + + QString domainName; + while (!resolvconf.atEnd()) { + QByteArray line = resolvconf.readLine().trimmed(); + if (line.startsWith("domain ")) + return QUrl::fromAce(line.mid(sizeof "domain " - 1).trimmed()); + + // in case there's no "domain" line, fall back to the first "search" entry + if (domainName.isEmpty() && line.startsWith("search ")) { + QByteArray searchDomain = line.mid(sizeof "search " - 1).trimmed(); + int pos = searchDomain.indexOf(' '); + if (pos != -1) + searchDomain.truncate(pos); + domainName = QUrl::fromAce(searchDomain); + } + } + + // return the fallen-back-to searched domain + return domainName; +} + +QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo_win.cpp b/src/network/kernel/qhostinfo_win.cpp new file mode 100644 index 0000000000..58f309b80b --- /dev/null +++ b/src/network/kernel/qhostinfo_win.cpp @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** 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 <winsock2.h> + +#include "qhostinfo_p.h" +#include "private/qnativesocketengine_p.h" +#include <ws2tcpip.h> +#include <private/qsystemlibrary_p.h> +#include <qmutex.h> +#include <qurl.h> +#include <private/qmutexpool_p.h> + +QT_BEGIN_NAMESPACE + +//#define QHOSTINFO_DEBUG + +// Older SDKs do not include the addrinfo struct declaration, so we +// include a copy of it here. +struct qt_addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + char *ai_canonname; + sockaddr *ai_addr; + qt_addrinfo *ai_next; +}; + +//### +#define QT_SOCKLEN_T int +#ifndef NI_MAXHOST // already defined to 1025 in ws2tcpip.h? +#define NI_MAXHOST 1024 +#endif + +typedef int (__stdcall *getnameinfoProto)(const sockaddr *, QT_SOCKLEN_T, const char *, DWORD, const char *, DWORD, int); +typedef int (__stdcall *getaddrinfoProto)(const char *, const char *, const qt_addrinfo *, qt_addrinfo **); +typedef int (__stdcall *freeaddrinfoProto)(qt_addrinfo *); +static getnameinfoProto local_getnameinfo = 0; +static getaddrinfoProto local_getaddrinfo = 0; +static freeaddrinfoProto local_freeaddrinfo = 0; + +static void resolveLibrary() +{ + // Attempt to resolve getaddrinfo(); without it we'll have to fall + // back to gethostbyname(), which has no IPv6 support. +#if !defined(Q_OS_WINCE) + local_getaddrinfo = (getaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2_32"), "getaddrinfo"); + local_freeaddrinfo = (freeaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2_32"), "freeaddrinfo"); + local_getnameinfo = (getnameinfoProto) QSystemLibrary::resolve(QLatin1String("ws2_32"), "getnameinfo"); +#else + local_getaddrinfo = (getaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "getaddrinfo"); + local_freeaddrinfo = (freeaddrinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "freeaddrinfo"); + local_getnameinfo = (getnameinfoProto) QSystemLibrary::resolve(QLatin1String("ws2"), "getnameinfo"); +#endif +} + +#if defined(Q_OS_WINCE) +#include <qmutex.h> +QMutex qPrivCEMutex; +#endif + +QHostInfo QHostInfoAgent::fromName(const QString &hostName) +{ +#if defined(Q_OS_WINCE) + QMutexLocker locker(&qPrivCEMutex); +#endif + QWindowsSockInit winSock; + + // Load res_init on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(QMutexPool::globalInstanceGet(&local_getaddrinfo)); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + QHostInfo results; + +#if defined(QHOSTINFO_DEBUG) + qDebug("QHostInfoAgent::fromName(%p): looking up \"%s\" (IPv6 support is %s)", + this, hostName.toLatin1().constData(), + (local_getaddrinfo && local_freeaddrinfo) ? "enabled" : "disabled"); +#endif + + QHostAddress address; + if (address.setAddress(hostName)) { + // Reverse lookup + if (local_getnameinfo) { + sockaddr_in sa4; + qt_sockaddr_in6 sa6; + sockaddr *sa; + QT_SOCKLEN_T saSize; + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + sa = (sockaddr *)&sa4; + saSize = sizeof(sa4); + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + sa4.sin_addr.s_addr = htonl(address.toIPv4Address()); + } else { + sa = (sockaddr *)&sa6; + saSize = sizeof(sa6); + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + memcpy(sa6.sin6_addr.qt_s6_addr, address.toIPv6Address().c, sizeof(sa6.sin6_addr.qt_s6_addr)); + } + + char hbuf[NI_MAXHOST]; + if (local_getnameinfo(sa, saSize, hbuf, sizeof(hbuf), 0, 0, 0) == 0) + results.setHostName(QString::fromLatin1(hbuf)); + } else { + unsigned long addr = inet_addr(hostName.toLatin1().constData()); + struct hostent *ent = gethostbyaddr((const char*)&addr, sizeof(addr), AF_INET); + if (ent) + results.setHostName(QString::fromLatin1(ent->h_name)); + } + + if (results.hostName().isEmpty()) + results.setHostName(address.toString()); + results.setAddresses(QList<QHostAddress>() << address); + return results; + } + + // IDN support + QByteArray aceHostname = QUrl::toAce(hostName); + results.setHostName(hostName); + if (aceHostname.isEmpty()) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(hostName.isEmpty() ? tr("No host name given") : tr("Invalid hostname")); + return results; + } + + if (local_getaddrinfo && local_freeaddrinfo) { + // Call getaddrinfo, and place all IPv4 addresses at the start + // and the IPv6 addresses at the end of the address list in + // results. + qt_addrinfo *res; + int err = local_getaddrinfo(aceHostname.constData(), 0, 0, &res); + if (err == 0) { + QList<QHostAddress> addresses; + for (qt_addrinfo *p = res; p != 0; p = p->ai_next) { + switch (p->ai_family) { + case AF_INET: { + QHostAddress addr; + addr.setAddress(ntohl(((sockaddr_in *) p->ai_addr)->sin_addr.s_addr)); + if (!addresses.contains(addr)) + addresses.append(addr); + } + break; + case AF_INET6: { + QHostAddress addr; + addr.setAddress(((qt_sockaddr_in6 *) p->ai_addr)->sin6_addr.qt_s6_addr); + if (!addresses.contains(addr)) + addresses.append(addr); + } + break; + default: + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown address type")); + } + } + results.setAddresses(addresses); + local_freeaddrinfo(res); + } else if (WSAGetLastError() == WSAHOST_NOT_FOUND || WSAGetLastError() == WSANO_DATA) { + results.setError(QHostInfo::HostNotFound); + results.setErrorString(tr("Host not found")); + } else { + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown error")); + } + } else { + // Fall back to gethostbyname, which only supports IPv4. + hostent *ent = gethostbyname(aceHostname.constData()); + if (ent) { + char **p; + QList<QHostAddress> addresses; + switch (ent->h_addrtype) { + case AF_INET: + for (p = ent->h_addr_list; *p != 0; p++) { + long *ip4Addr = (long *) *p; + QHostAddress temp; + temp.setAddress(ntohl(*ip4Addr)); + addresses << temp; + } + break; + default: + results.setError(QHostInfo::UnknownError); + results.setErrorString(tr("Unknown address type")); + break; + } + results.setAddresses(addresses); + } else if (WSAGetLastError() == 11001) { + results.setErrorString(tr("Host not found")); + results.setError(QHostInfo::HostNotFound); + } else { + results.setErrorString(tr("Unknown error")); + results.setError(QHostInfo::UnknownError); + } + } + +#if defined(QHOSTINFO_DEBUG) + if (results.error() != QHostInfo::NoError) { + qDebug("QHostInfoAgent::run(%p): error (%s)", + this, results.errorString().toLatin1().constData()); + } else { + QString tmp; + QList<QHostAddress> addresses = results.addresses(); + for (int i = 0; i < addresses.count(); ++i) { + if (i != 0) tmp += ", "; + tmp += addresses.at(i).toString(); + } + qDebug("QHostInfoAgent::run(%p): found %i entries: {%s}", + this, addresses.count(), tmp.toLatin1().constData()); + } +#endif + return results; +} + +QString QHostInfo::localHostName() +{ + QWindowsSockInit winSock; + + char hostName[512]; + if (gethostname(hostName, sizeof(hostName)) == -1) + return QString(); + hostName[sizeof(hostName) - 1] = '\0'; + return QString::fromLocal8Bit(hostName); +} + +// QString QHostInfo::localDomainName() defined in qnetworkinterface_win.cpp + +QT_END_NAMESPACE diff --git a/src/network/kernel/qnetworkinterface.cpp b/src/network/kernel/qnetworkinterface.cpp new file mode 100644 index 0000000000..e72bc65c2b --- /dev/null +++ b/src/network/kernel/qnetworkinterface.cpp @@ -0,0 +1,619 @@ +/**************************************************************************** +** +** 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 "qnetworkinterface.h" +#include "qnetworkinterface_p.h" + +#include "qdebug.h" +#include "qendian.h" + +#ifndef QT_NO_NETWORKINTERFACE + +QT_BEGIN_NAMESPACE + +static QList<QNetworkInterfacePrivate *> postProcess(QList<QNetworkInterfacePrivate *> list) +{ + // Some platforms report a netmask but don't report a broadcast address + // Go through all available addresses and calculate the broadcast address + // from the IP and the netmask + // + // This is an IPv4-only thing -- IPv6 has no concept of broadcasts + // The math is: + // broadcast = IP | ~netmask + + QList<QNetworkInterfacePrivate *>::Iterator it = list.begin(); + const QList<QNetworkInterfacePrivate *>::Iterator end = list.end(); + for ( ; it != end; ++it) { + QList<QNetworkAddressEntry>::Iterator addr_it = (*it)->addressEntries.begin(); + const QList<QNetworkAddressEntry>::Iterator addr_end = (*it)->addressEntries.end(); + for ( ; addr_it != addr_end; ++addr_it) { + if (addr_it->ip().protocol() != QAbstractSocket::IPv4Protocol) + continue; + + if (!addr_it->netmask().isNull() && addr_it->broadcast().isNull()) { + QHostAddress bcast = addr_it->ip(); + bcast = QHostAddress(bcast.toIPv4Address() | ~addr_it->netmask().toIPv4Address()); + addr_it->setBroadcast(bcast); + } + } + } + + return list; +} + +Q_GLOBAL_STATIC(QNetworkInterfaceManager, manager) + +QNetworkInterfaceManager::QNetworkInterfaceManager() +{ +} + +QNetworkInterfaceManager::~QNetworkInterfaceManager() +{ +} + +QSharedDataPointer<QNetworkInterfacePrivate> QNetworkInterfaceManager::interfaceFromName(const QString &name) +{ + QList<QSharedDataPointer<QNetworkInterfacePrivate> > interfaceList = allInterfaces(); + QList<QSharedDataPointer<QNetworkInterfacePrivate> >::ConstIterator it = interfaceList.constBegin(); + for ( ; it != interfaceList.constEnd(); ++it) + if ((*it)->name == name) + return *it; + + return empty; +} + +QSharedDataPointer<QNetworkInterfacePrivate> QNetworkInterfaceManager::interfaceFromIndex(int index) +{ + QList<QSharedDataPointer<QNetworkInterfacePrivate> > interfaceList = allInterfaces(); + QList<QSharedDataPointer<QNetworkInterfacePrivate> >::ConstIterator it = interfaceList.constBegin(); + for ( ; it != interfaceList.constEnd(); ++it) + if ((*it)->index == index) + return *it; + + return empty; +} + +QList<QSharedDataPointer<QNetworkInterfacePrivate> > QNetworkInterfaceManager::allInterfaces() +{ + QList<QNetworkInterfacePrivate *> list = postProcess(scan()); + QList<QSharedDataPointer<QNetworkInterfacePrivate> > result; + + foreach (QNetworkInterfacePrivate *ptr, list) + result << QSharedDataPointer<QNetworkInterfacePrivate>(ptr); + + return result; +} + +QString QNetworkInterfacePrivate::makeHwAddress(int len, uchar *data) +{ + QString result; + for (int i = 0; i < len; ++i) { + if (i) + result += QLatin1Char(':'); + + char buf[3]; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && defined(_MSC_VER) && _MSC_VER >= 1400 + sprintf_s(buf, 3, "%02hX", ushort(data[i])); +#else + sprintf(buf, "%02hX", ushort(data[i])); +#endif + result += QLatin1String(buf); + } + return result; +} + +/*! + \class QNetworkAddressEntry + \brief The QNetworkAddressEntry class stores one IP address + supported by a network interface, along with its associated + netmask and broadcast address. + + \since 4.2 + \reentrant + \ingroup network + + Each network interface can contain zero or more IP addresses, which + in turn can be associated with a netmask and/or a broadcast + address (depending on support from the operating system). + + This class represents one such group. +*/ + +/*! + Constructs an empty QNetworkAddressEntry object. +*/ +QNetworkAddressEntry::QNetworkAddressEntry() + : d(new QNetworkAddressEntryPrivate) +{ +} + +/*! + Constructs a QNetworkAddressEntry object that is a copy of the + object \a other. +*/ +QNetworkAddressEntry::QNetworkAddressEntry(const QNetworkAddressEntry &other) + : d(new QNetworkAddressEntryPrivate(*other.d.data())) +{ +} + +/*! + Makes a copy of the QNetworkAddressEntry object \a other. +*/ +QNetworkAddressEntry &QNetworkAddressEntry::operator=(const QNetworkAddressEntry &other) +{ + *d.data() = *other.d.data(); + return *this; +} + +/*! + Destroys this QNetworkAddressEntry object. +*/ +QNetworkAddressEntry::~QNetworkAddressEntry() +{ +} + +/*! + Returns true if this network address entry is the same as \a + other. +*/ +bool QNetworkAddressEntry::operator==(const QNetworkAddressEntry &other) const +{ + if (d == other.d) return true; + if (!d || !other.d) return false; + return d->address == other.d->address && + d->netmask == other.d->netmask && + d->broadcast == other.d->broadcast; +} + +/*! + \fn bool QNetworkAddressEntry::operator!=(const QNetworkAddressEntry &other) const + + Returns true if this network address entry is different from \a + other. +*/ + +/*! + This function returns one IPv4 or IPv6 address found, that was + found in a network interface. +*/ +QHostAddress QNetworkAddressEntry::ip() const +{ + return d->address; +} + +/*! + Sets the IP address the QNetworkAddressEntry object contains to \a + newIp. +*/ +void QNetworkAddressEntry::setIp(const QHostAddress &newIp) +{ + d->address = newIp; +} + +/*! + Returns the netmask associated with the IP address. The + netmask is expressed in the form of an IP address, such as + 255.255.0.0. + + For IPv6 addresses, the prefix length is converted to an address + where the number of bits set to 1 is equal to the prefix + length. For a prefix length of 64 bits (the most common value), + the netmask will be expressed as a QHostAddress holding the + address FFFF:FFFF:FFFF:FFFF:: + + \sa prefixLength() +*/ +QHostAddress QNetworkAddressEntry::netmask() const +{ + return d->netmask; +} + +/*! + Sets the netmask that this QNetworkAddressEntry object contains to + \a newNetmask. Setting the netmask also sets the prefix length to + match the new netmask. + + \sa setPrefixLength() +*/ +void QNetworkAddressEntry::setNetmask(const QHostAddress &newNetmask) +{ + if (newNetmask.protocol() != ip().protocol()) { + d->netmask = QNetmaskAddress(); + return; + } + + d->netmask.setAddress(newNetmask); +} + +/*! + \since 4.5 + Returns the prefix length of this IP address. The prefix length + matches the number of bits set to 1 in the netmask (see + netmask()). For IPv4 addresses, the value is between 0 and 32. For + IPv6 addresses, it's contained between 0 and 128 and is the + preferred form of representing addresses. + + This function returns -1 if the prefix length could not be + determined (i.e., netmask() returns a null QHostAddress()). + + \sa netmask() +*/ +int QNetworkAddressEntry::prefixLength() const +{ + return d->netmask.prefixLength(); +} + +/*! + \since 4.5 + Sets the prefix length of this IP address to \a length. The value + of \a length must be valid for this type of IP address: between 0 + and 32 for IPv4 addresses, between 0 and 128 for IPv6 + addresses. Setting to any invalid value is equivalent to setting + to -1, which means "no prefix length". + + Setting the prefix length also sets the netmask (see netmask()). + + \sa setNetmask() +*/ +void QNetworkAddressEntry::setPrefixLength(int length) +{ + d->netmask.setPrefixLength(d->address.protocol(), length); +} + +/*! + Returns the broadcast address associated with the IPv4 + address and netmask. It can usually be derived from those two by + setting to 1 the bits of the IP address where the netmask contains + a 0. (In other words, by bitwise-OR'ing the IP address with the + inverse of the netmask) + + This member is always empty for IPv6 addresses, since the concept + of broadcast has been abandoned in that system in favor of + multicast. In particular, the group of hosts corresponding to all + the nodes in the local network can be reached by the "all-nodes" + special multicast group (address FF02::1). +*/ +QHostAddress QNetworkAddressEntry::broadcast() const +{ + return d->broadcast; +} + +/*! + Sets the broadcast IP address of this QNetworkAddressEntry object + to \a newBroadcast. +*/ +void QNetworkAddressEntry::setBroadcast(const QHostAddress &newBroadcast) +{ + d->broadcast = newBroadcast; +} + +/*! + \class QNetworkInterface + \brief The QNetworkInterface class provides a listing of the host's IP + addresses and network interfaces. + + \since 4.2 + \reentrant + \ingroup network + + QNetworkInterface represents one network interface attached to the + host where the program is being run. Each network interface may + contain zero or more IP addresses, each of which is optionally + associated with a netmask and/or a broadcast address. The list of + such trios can be obtained with addressEntries(). Alternatively, + when the netmask or the broadcast addresses aren't necessary, use + the allAddresses() convenience function to obtain just the IP + addresses. + + QNetworkInterface also reports the interface's hardware address with + hardwareAddress(). + + Not all operating systems support reporting all features. Only the + IPv4 addresses are guaranteed to be listed by this class in all + platforms. In particular, IPv6 address listing is only supported + on Windows XP and more recent versions, Linux, MacOS X and the + BSDs. + + \sa QNetworkAddressEntry +*/ + +/*! + \enum QNetworkInterface::InterfaceFlag + Specifies the flags associated with this network interface. The + possible values are: + + \value IsUp the network interface is active + \value IsRunning the network interface has resources + allocated + \value CanBroadcast the network interface works in + broadcast mode + \value IsLoopBack the network interface is a loopback + interface: that is, it's a virtual + interface whose destination is the + host computer itself + \value IsPointToPoint the network interface is a + point-to-point interface: that is, + there is one, single other address + that can be directly reached by it. + \value CanMulticast the network interface supports + multicasting + + Note that one network interface cannot be both broadcast-based and + point-to-point. +*/ + +/*! + Constructs an empty network interface object. +*/ +QNetworkInterface::QNetworkInterface() + : d(0) +{ +} + +/*! + Frees the resources associated with the QNetworkInterface object. +*/ +QNetworkInterface::~QNetworkInterface() +{ +} + +/*! + Creates a copy of the QNetworkInterface object contained in \a + other. +*/ +QNetworkInterface::QNetworkInterface(const QNetworkInterface &other) + : d(other.d) +{ +} + +/*! + Copies the contents of the QNetworkInterface object contained in \a + other into this one. +*/ +QNetworkInterface &QNetworkInterface::operator=(const QNetworkInterface &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this QNetworkInterface object contains valid + information about a network interface. +*/ +bool QNetworkInterface::isValid() const +{ + return !name().isEmpty(); +} + +/*! + \since 4.5 + Returns the interface system index, if known. This is an integer + assigned by the operating system to identify this interface and it + generally doesn't change. It matches the scope ID field in IPv6 + addresses. + + If the index isn't known, this function returns 0. +*/ +int QNetworkInterface::index() const +{ + return d ? d->index : 0; +} + +/*! + Returns the name of this network interface. On Unix systems, this + is a string containing the type of the interface and optionally a + sequence number, such as "eth0", "lo" or "pcn0". On Windows, it's + an internal ID that cannot be changed by the user. +*/ +QString QNetworkInterface::name() const +{ + return d ? d->name : QString(); +} + +/*! + \since 4.5 + + Returns the human-readable name of this network interface on + Windows, such as "Local Area Connection", if the name could be + determined. If it couldn't, this function returns the same as + name(). The human-readable name is a name that the user can modify + in the Windows Control Panel, so it may change during the + execution of the program. + + On Unix, this function currently always returns the same as + name(), since Unix systems don't store a configuration for + human-readable names. +*/ +QString QNetworkInterface::humanReadableName() const +{ + return d ? !d->friendlyName.isEmpty() ? d->friendlyName : name() : QString(); +} + +/*! + Returns the flags associated with this network interface. +*/ +QNetworkInterface::InterfaceFlags QNetworkInterface::flags() const +{ + return d ? d->flags : InterfaceFlags(0); +} + +/*! + Returns the low-level hardware address for this interface. On + Ethernet interfaces, this will be a MAC address in string + representation, separated by colons. + + Other interface types may have other types of hardware + addresses. Implementations should not depend on this function + returning a valid MAC address. +*/ +QString QNetworkInterface::hardwareAddress() const +{ + return d ? d->hardwareAddress : QString(); +} + +/*! + Returns the list of IP addresses that this interface possesses + along with their associated netmasks and broadcast addresses. + + If the netmask or broadcast address information is not necessary, + you can call the allAddresses() function to obtain just the IP + addresses. +*/ +QList<QNetworkAddressEntry> QNetworkInterface::addressEntries() const +{ + return d ? d->addressEntries : QList<QNetworkAddressEntry>(); +} + +/*! + Returns a QNetworkInterface object for the interface named \a + name. If no such interface exists, this function returns an + invalid QNetworkInterface object. + + \sa name(), isValid() +*/ +QNetworkInterface QNetworkInterface::interfaceFromName(const QString &name) +{ + QNetworkInterface result; + result.d = manager()->interfaceFromName(name); + return result; +} + +/*! + Returns a QNetworkInterface object for the interface whose internal + ID is \a index. Network interfaces have a unique identifier called + the "interface index" to distinguish it from other interfaces on + the system. Often, this value is assigned progressively and + interfaces being removed and then added again get a different + value every time. + + This index is also found in the IPv6 address' scope ID field. +*/ +QNetworkInterface QNetworkInterface::interfaceFromIndex(int index) +{ + QNetworkInterface result; + result.d = manager()->interfaceFromIndex(index); + return result; +} + +/*! + Returns a listing of all the network interfaces found on the host + machine. +*/ +QList<QNetworkInterface> QNetworkInterface::allInterfaces() +{ + QList<QSharedDataPointer<QNetworkInterfacePrivate> > privs = manager()->allInterfaces(); + QList<QNetworkInterface> result; + foreach (const QSharedDataPointer<QNetworkInterfacePrivate> &p, privs) { + QNetworkInterface item; + item.d = p; + result << item; + } + + return result; +} + +/*! + This convenience function returns all IP addresses found on the + host machine. It is equivalent to calling addressEntries() on all the + objects returned by allInterfaces() to obtain lists of QHostAddress + objects then calling QHostAddress::ip() on each of these. +*/ +QList<QHostAddress> QNetworkInterface::allAddresses() +{ + QList<QSharedDataPointer<QNetworkInterfacePrivate> > privs = manager()->allInterfaces(); + QList<QHostAddress> result; + foreach (const QSharedDataPointer<QNetworkInterfacePrivate> &p, privs) { + foreach (const QNetworkAddressEntry &entry, p->addressEntries) + result += entry.ip(); + } + + return result; +} + +#ifndef QT_NO_DEBUG_STREAM +static inline QDebug flagsDebug(QDebug debug, QNetworkInterface::InterfaceFlags flags) +{ + if (flags & QNetworkInterface::IsUp) + debug.nospace() << "IsUp "; + if (flags & QNetworkInterface::IsRunning) + debug.nospace() << "IsRunning "; + if (flags & QNetworkInterface::CanBroadcast) + debug.nospace() << "CanBroadcast "; + if (flags & QNetworkInterface::IsLoopBack) + debug.nospace() << "IsLoopBack "; + if (flags & QNetworkInterface::IsPointToPoint) + debug.nospace() << "IsPointToPoint "; + if (flags & QNetworkInterface::CanMulticast) + debug.nospace() << "CanMulticast "; + return debug.nospace(); +} + +static inline QDebug operator<<(QDebug debug, const QNetworkAddressEntry &entry) +{ + debug.nospace() << "(address = " << entry.ip(); + if (!entry.netmask().isNull()) + debug.nospace() << ", netmask = " << entry.netmask(); + if (!entry.broadcast().isNull()) + debug.nospace() << ", broadcast = " << entry.broadcast(); + debug.nospace() << ')'; + return debug.space(); +} + +QDebug operator<<(QDebug debug, const QNetworkInterface &networkInterface) +{ + debug.nospace() << "QNetworkInterface(name = " << networkInterface.name() + << ", hardware address = " << networkInterface.hardwareAddress() + << ", flags = "; + flagsDebug(debug, networkInterface.flags()); +#if defined(Q_CC_RVCT) + // RVCT gets confused with << networkInterface.addressEntries(), reason unknown. + debug.nospace() << ")\n"; +#else + debug.nospace() << ", entries = " << networkInterface.addressEntries() + << ")\n"; +#endif + return debug.space(); +} +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE diff --git a/src/network/kernel/qnetworkinterface.h b/src/network/kernel/qnetworkinterface.h new file mode 100644 index 0000000000..d65a6d70a5 --- /dev/null +++ b/src/network/kernel/qnetworkinterface.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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 QNETWORKINTERFACE_H +#define QNETWORKINTERFACE_H + +#include <QtCore/qshareddata.h> +#include <QtCore/qscopedpointer.h> +#include <QtNetwork/qhostaddress.h> + +#ifndef QT_NO_NETWORKINTERFACE + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +template<typename T> class QList; + +class QNetworkAddressEntryPrivate; +class Q_NETWORK_EXPORT QNetworkAddressEntry +{ +public: + QNetworkAddressEntry(); + QNetworkAddressEntry(const QNetworkAddressEntry &other); + QNetworkAddressEntry &operator=(const QNetworkAddressEntry &other); + ~QNetworkAddressEntry(); + bool operator==(const QNetworkAddressEntry &other) const; + inline bool operator!=(const QNetworkAddressEntry &other) const + { return !(*this == other); } + + QHostAddress ip() const; + void setIp(const QHostAddress &newIp); + + QHostAddress netmask() const; + void setNetmask(const QHostAddress &newNetmask); + int prefixLength() const; + void setPrefixLength(int length); + + QHostAddress broadcast() const; + void setBroadcast(const QHostAddress &newBroadcast); + +private: + QScopedPointer<QNetworkAddressEntryPrivate> d; +}; + +class QNetworkInterfacePrivate; +class Q_NETWORK_EXPORT QNetworkInterface +{ +public: + enum InterfaceFlag { + IsUp = 0x1, + IsRunning = 0x2, + CanBroadcast = 0x4, + IsLoopBack = 0x8, + IsPointToPoint = 0x10, + CanMulticast = 0x20 + }; + Q_DECLARE_FLAGS(InterfaceFlags, InterfaceFlag) + + QNetworkInterface(); + QNetworkInterface(const QNetworkInterface &other); + QNetworkInterface &operator=(const QNetworkInterface &other); + ~QNetworkInterface(); + + bool isValid() const; + + int index() const; + QString name() const; + QString humanReadableName() const; + InterfaceFlags flags() const; + QString hardwareAddress() const; + QList<QNetworkAddressEntry> addressEntries() const; + + static QNetworkInterface interfaceFromName(const QString &name); + static QNetworkInterface interfaceFromIndex(int index); + static QList<QNetworkInterface> allInterfaces(); + static QList<QHostAddress> allAddresses(); + +private: + friend class QNetworkInterfacePrivate; + QSharedDataPointer<QNetworkInterfacePrivate> d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QNetworkInterface::InterfaceFlags) + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QNetworkInterface &networkInterface); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_NETWORKINTERFACE + +#endif diff --git a/src/network/kernel/qnetworkinterface_p.h b/src/network/kernel/qnetworkinterface_p.h new file mode 100644 index 0000000000..0136593b8a --- /dev/null +++ b/src/network/kernel/qnetworkinterface_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** 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 QNETWORKINTERFACEPRIVATE_H +#define QNETWORKINTERFACEPRIVATE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qatomic.h> +#include <QtCore/qlist.h> +#include <QtCore/qreadwritelock.h> +#include <QtCore/qstring.h> +#include <QtNetwork/qhostaddress.h> +#include <QtNetwork/qabstractsocket.h> +#include <private/qhostaddress_p.h> + +#ifndef QT_NO_NETWORKINTERFACE + +QT_BEGIN_NAMESPACE + +class QNetworkAddressEntryPrivate +{ +public: + QHostAddress address; + QNetmaskAddress netmask; + QHostAddress broadcast; +}; + +class QNetworkInterfacePrivate: public QSharedData +{ +public: + QNetworkInterfacePrivate() : index(0), flags(0) + { } + ~QNetworkInterfacePrivate() + { } + + int index; // interface index, if know + QNetworkInterface::InterfaceFlags flags; + + QString name; + QString friendlyName; + QString hardwareAddress; + + QList<QNetworkAddressEntry> addressEntries; + + static QString makeHwAddress(int len, uchar *data); + +private: + // disallow copying -- avoid detaching + QNetworkInterfacePrivate &operator=(const QNetworkInterfacePrivate &other); + QNetworkInterfacePrivate(const QNetworkInterfacePrivate &other); +}; + +class QNetworkInterfaceManager +{ +public: + QNetworkInterfaceManager(); + ~QNetworkInterfaceManager(); + + QSharedDataPointer<QNetworkInterfacePrivate> interfaceFromName(const QString &name); + QSharedDataPointer<QNetworkInterfacePrivate> interfaceFromIndex(int index); + QList<QSharedDataPointer<QNetworkInterfacePrivate> > allInterfaces(); + + // convenience: + QSharedDataPointer<QNetworkInterfacePrivate> empty; + +private: + QList<QNetworkInterfacePrivate *> scan(); +}; + + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE + +#endif diff --git a/src/network/kernel/qnetworkinterface_symbian.cpp b/src/network/kernel/qnetworkinterface_symbian.cpp new file mode 100644 index 0000000000..7767f54085 --- /dev/null +++ b/src/network/kernel/qnetworkinterface_symbian.cpp @@ -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 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 QNETWORKINTERFACE_DEBUG + +#include "qnetworkinterface.h" +#include "qnetworkinterface_p.h" +#include <private/qcore_symbian_p.h> + +#ifndef QT_NO_NETWORKINTERFACE + +#include <in_sock.h> +#include <in_iface.h> +#include <es_sock.h> + +QT_BEGIN_NAMESPACE + + +static QNetworkInterface::InterfaceFlags convertFlags(const TSoInetInterfaceInfo& aInfo) +{ + QNetworkInterface::InterfaceFlags flags = 0; + flags |= (aInfo.iState == EIfUp) ? QNetworkInterface::IsUp : QNetworkInterface::InterfaceFlag(0); + // We do not have separate flag for running in Symbian OS + flags |= (aInfo.iState == EIfUp) ? QNetworkInterface::IsRunning : QNetworkInterface::InterfaceFlag(0); + flags |= (aInfo.iFeatures & KIfCanBroadcast) ? QNetworkInterface::CanBroadcast : QNetworkInterface::InterfaceFlag(0); + flags |= (aInfo.iFeatures & KIfIsLoopback) ? QNetworkInterface::IsLoopBack : QNetworkInterface::InterfaceFlag(0); + flags |= (aInfo.iFeatures & KIfIsPointToPoint) ? QNetworkInterface::IsPointToPoint : QNetworkInterface::InterfaceFlag(0); + flags |= (aInfo.iFeatures & KIfCanMulticast) ? QNetworkInterface::CanMulticast : QNetworkInterface::InterfaceFlag(0); + return flags; +} + +//TODO: share this, at least QHostInfo needs to do the same thing +static QHostAddress qt_QHostAddressFromTInetAddr(const TInetAddr& addr) +{ + //TODO: do we want to call v4 mapped addresses v4 or v6 outside of this file? + if (addr.IsV4Mapped() || addr.Family() == KAfInet) { + //convert v4 host address + return QHostAddress(addr.Address()); + } else { + //convert v6 host address + return QHostAddress((quint8 *)(addr.Ip6Address().u.iAddr8)); + } +} + +static QList<QNetworkInterfacePrivate *> interfaceListing() +{ + TInt err(KErrNone); + QList<QNetworkInterfacePrivate *> interfaces; + QList<QHostAddress> addressesWithEstimatedNetmasks; + + // Open dummy socket for interface queries + RSocket socket; + err = socket.Open(qt_symbianGetSocketServer(), _L("udp")); + if (err) { + return interfaces; + } + + // Ask socket to start enumerating interfaces + err = socket.SetOpt(KSoInetEnumInterfaces, KSolInetIfCtrl); + if (err) { + socket.Close(); + return interfaces; + } + + int ifindex = 0; + TPckgBuf<TSoInetInterfaceInfo> infoPckg; + TSoInetInterfaceInfo &info = infoPckg(); + while (socket.GetOpt(KSoInetNextInterface, KSolInetIfCtrl, infoPckg) == KErrNone) { + if (info.iName != KNullDesC) { + TName address; + QNetworkAddressEntry entry; + QNetworkInterfacePrivate *iface = 0; + + iface = new QNetworkInterfacePrivate; + iface->index = ifindex++; + interfaces << iface; + iface->name = qt_TDesC2QString(info.iName); + iface->flags = convertFlags(info); + + if (/*info.iFeatures&KIfHasHardwareAddr &&*/ info.iHwAddr.Family() != KAFUnspec) { + for (TInt i = sizeof(SSockAddr); i < sizeof(SSockAddr) + info.iHwAddr.GetUserLen(); i++) { + address.AppendNumFixedWidth(info.iHwAddr[i], EHex, 2); + if ((i + 1) < sizeof(SSockAddr) + info.iHwAddr.GetUserLen()) + address.Append(_L(":")); + } + address.UpperCase(); + iface->hardwareAddress = qt_TDesC2QString(address); + } + + // Get the address of the interface + entry.setIp(qt_QHostAddressFromTInetAddr(info.iAddress)); + +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "address is" << info.iAddress.Family() << entry.ip(); + qDebug() << "netmask is" << info.iNetMask.Family() << qt_QHostAddressFromTInetAddr( info.iNetMask ); +#endif + + // Get the interface netmask + if (info.iNetMask.IsUnspecified()) { + // For some reason netmask is always 0.0.0.0 for IPv4 interfaces + // and loopback interfaces (which we statically know) + if (info.iAddress.IsV4Mapped()) { + if (info.iFeatures & KIfIsLoopback) { + entry.setPrefixLength(32); + } else { + // Workaround: Let Symbian determine netmask based on IP address class (IPv4 only API) + TInetAddr netmask; + netmask.NetMask(info.iAddress); + entry.setNetmask(QHostAddress(netmask.Address())); //binary convert v4 address + addressesWithEstimatedNetmasks << entry.ip(); +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "address class determined netmask" << entry.netmask(); +#endif + } + } else { + // For IPv6 interfaces + if (info.iFeatures & KIfIsLoopback) { + entry.setPrefixLength(128); + } else if (info.iNetMask.IsUnspecified()) { + //Don't see this error for IPv6, but try to handle it if it happens + entry.setPrefixLength(64); //most common +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "total guess netmask" << entry.netmask(); +#endif + addressesWithEstimatedNetmasks << entry.ip(); + } + } + } else { + //Expected code path for IPv6 non loopback interfaces (IPv4 could come here if symbian is fixed) + entry.setNetmask(qt_QHostAddressFromTInetAddr(info.iNetMask)); +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "reported netmask" << entry.netmask(); +#endif + } + + // broadcast address is determined from the netmask in postProcess() + + // Add new entry to interface address entries + iface->addressEntries << entry; + +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug("\n Found network interface %s, interface flags:\n\ + IsUp = %d, IsRunning = %d, CanBroadcast = %d,\n\ + IsLoopBack = %d, IsPointToPoint = %d, CanMulticast = %d, \n\ + ip = %s, netmask = %s, broadcast = %s,\n\ + hwaddress = %s", + iface->name.toLatin1().constData(), + iface->flags & QNetworkInterface::IsUp, iface->flags & QNetworkInterface::IsRunning, iface->flags & QNetworkInterface::CanBroadcast, + iface->flags & QNetworkInterface::IsLoopBack, iface->flags & QNetworkInterface::IsPointToPoint, iface->flags & QNetworkInterface::CanMulticast, + entry.ip().toString().toLatin1().constData(), entry.netmask().toString().toLatin1().constData(), entry.broadcast().toString().toLatin1().constData(), + iface->hardwareAddress.toLatin1().constData()); +#endif + } + } + + // if we didn't have to guess any netmasks, then we're done. + if (addressesWithEstimatedNetmasks.isEmpty()) { + socket.Close(); + return interfaces; + } + + // we will try to use routing info to detect more precisely + // estimated netmasks and then ::postProcess() should calculate + // broadcast addresses + + // use dummy socket to start enumerating routes + err = socket.SetOpt(KSoInetEnumRoutes, KSolInetRtCtrl); + if (err) { + socket.Close(); + // return what we have + // up to this moment + return interfaces; + } + + TSoInetRouteInfo routeInfo; + TPckg<TSoInetRouteInfo> routeInfoPkg(routeInfo); + while (socket.GetOpt(KSoInetNextRoute, KSolInetRtCtrl, routeInfoPkg) == KErrNone) { + // get interface address + QHostAddress ifAddr(qt_QHostAddressFromTInetAddr(routeInfo.iIfAddr)); + if (ifAddr.isNull()) + continue; + if (!addressesWithEstimatedNetmasks.contains(ifAddr)) { +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "skipping route from" << ifAddr << "because it wasn't an estimated netmask"; +#endif + continue; + } + + QHostAddress destination(qt_QHostAddressFromTInetAddr(routeInfo.iDstAddr)); +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << "route from" << ifAddr << "to" << destination; +#endif + if (destination.isNull() || destination != ifAddr) + continue; + + // search interfaces + for (int ifindex = 0; ifindex < interfaces.size(); ++ifindex) { + QNetworkInterfacePrivate *iface = interfaces.at(ifindex); + for (int eindex = 0; eindex < iface->addressEntries.size(); ++eindex) { + QNetworkAddressEntry entry = iface->addressEntries.at(eindex); + if (entry.ip() != ifAddr) { + continue; + } else if (!routeInfo.iNetMask.IsUnspecified()) { + //the route may also return 0.0.0.0 netmask, in which case don't use it. + QHostAddress netmask(qt_QHostAddressFromTInetAddr(routeInfo.iNetMask)); + entry.setNetmask(netmask); +#if defined(QNETWORKINTERFACE_DEBUG) + qDebug() << " - route netmask" << routeInfo.iNetMask.Family() << netmask << " (using route determined netmask)"; +#endif + iface->addressEntries.replace(eindex, entry); + } + } + } + } + + socket.Close(); + + return interfaces; +} + +QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() +{ + return interfaceListing(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp new file mode 100644 index 0000000000..6098bdeee0 --- /dev/null +++ b/src/network/kernel/qnetworkinterface_unix.cpp @@ -0,0 +1,449 @@ +/**************************************************************************** +** +** 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 "qset.h" +#include "qnetworkinterface.h" +#include "qnetworkinterface_p.h" +#include "qalgorithms.h" +#include "private/qnet_unix_p.h" + +#ifndef QT_NO_NETWORKINTERFACE + +#define IP_MULTICAST // make AIX happy and define IFF_MULTICAST + +#include <sys/types.h> +#include <sys/socket.h> + +#ifdef Q_OS_SOLARIS +# include <sys/sockio.h> +#endif +#include <net/if.h> + +#ifndef QT_NO_GETIFADDRS +# include <ifaddrs.h> +#endif + +#ifdef QT_LINUXBASE +# include <arpa/inet.h> +# ifndef SIOCGIFBRDADDR +# define SIOCGIFBRDADDR 0x8919 +# endif +#endif // QT_LINUXBASE + +#include <qplatformdefs.h> + +QT_BEGIN_NAMESPACE + +static QHostAddress addressFromSockaddr(sockaddr *sa) +{ + QHostAddress address; + if (!sa) + return address; + + if (sa->sa_family == AF_INET) + address.setAddress(htonl(((sockaddr_in *)sa)->sin_addr.s_addr)); +#ifndef QT_NO_IPV6 + else if (sa->sa_family == AF_INET6) + address.setAddress(((sockaddr_in6 *)sa)->sin6_addr.s6_addr); +#endif + return address; + +} + +static QNetworkInterface::InterfaceFlags convertFlags(uint rawFlags) +{ + QNetworkInterface::InterfaceFlags flags = 0; + flags |= (rawFlags & IFF_UP) ? QNetworkInterface::IsUp : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_RUNNING) ? QNetworkInterface::IsRunning : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_BROADCAST) ? QNetworkInterface::CanBroadcast : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_LOOPBACK) ? QNetworkInterface::IsLoopBack : QNetworkInterface::InterfaceFlag(0); +#ifdef IFF_POINTOPOINT //cygwin doesn't define IFF_POINTOPOINT + flags |= (rawFlags & IFF_POINTOPOINT) ? QNetworkInterface::IsPointToPoint : QNetworkInterface::InterfaceFlag(0); +#endif + +#ifdef IFF_MULTICAST + flags |= (rawFlags & IFF_MULTICAST) ? QNetworkInterface::CanMulticast : QNetworkInterface::InterfaceFlag(0); +#endif + return flags; +} + +#ifdef QT_NO_GETIFADDRS +// getifaddrs not available + +static const int STORAGEBUFFER_GROWTH = 256; + +static QSet<QByteArray> interfaceNames(int socket) +{ + QSet<QByteArray> result; +#ifdef QT_NO_IPV6IFNAME + QByteArray storageBuffer; + struct ifconf interfaceList; + + forever { + // grow the storage buffer + storageBuffer.resize(storageBuffer.size() + STORAGEBUFFER_GROWTH); + interfaceList.ifc_buf = storageBuffer.data(); + interfaceList.ifc_len = storageBuffer.size(); + + // get the interface list + if (qt_safe_ioctl(socket, SIOCGIFCONF, &interfaceList) >= 0) { + if (int(interfaceList.ifc_len + sizeof(ifreq) + 64) < storageBuffer.size()) { + // if the buffer was big enough, break + storageBuffer.resize(interfaceList.ifc_len); + break; + } + } else { + // internal error + return result; + } + if (storageBuffer.size() > 100000) { + // out of space + return result; + } + } + + int interfaceCount = interfaceList.ifc_len / sizeof(ifreq); + for (int i = 0; i < interfaceCount; ++i) { + QByteArray name = QByteArray(interfaceList.ifc_req[i].ifr_name); + if (!name.isEmpty()) + result << name; + } + + return result; +#else + Q_UNUSED(socket); + + // use if_nameindex + struct if_nameindex *interfaceList = ::if_nameindex(); + for (struct if_nameindex *ptr = interfaceList; ptr && ptr->if_name; ++ptr) + result << ptr->if_name; + + if_freenameindex(interfaceList); + return result; +#endif +} + +static QNetworkInterfacePrivate *findInterface(int socket, QList<QNetworkInterfacePrivate *> &interfaces, + struct ifreq &req) +{ + QNetworkInterfacePrivate *iface = 0; + int ifindex = 0; + +#ifndef QT_NO_IPV6IFNAME + // Get the interface index + ifindex = if_nametoindex(req.ifr_name); + + // find the interface data + QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin(); + for ( ; if_it != interfaces.end(); ++if_it) + if ((*if_it)->index == ifindex) { + // existing interface + iface = *if_it; + break; + } +#else + // Search by name + QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin(); + for ( ; if_it != interfaces.end(); ++if_it) + if ((*if_it)->name == QLatin1String(req.ifr_name)) { + // existing interface + iface = *if_it; + break; + } +#endif + + if (!iface) { + // new interface, create data: + iface = new QNetworkInterfacePrivate; + iface->index = ifindex; + interfaces << iface; + +#ifdef SIOCGIFNAME + // Get the canonical name + QByteArray oldName = req.ifr_name; + if (qt_safe_ioctl(socket, SIOCGIFNAME, &req) >= 0) { + iface->name = QString::fromLatin1(req.ifr_name); + + // reset the name: + memcpy(req.ifr_name, oldName, qMin<int>(oldName.length() + 1, sizeof(req.ifr_name) - 1)); + } else +#endif + { + // use this name anyways + iface->name = QString::fromLatin1(req.ifr_name); + } + + // Get interface flags + if (qt_safe_ioctl(socket, SIOCGIFFLAGS, &req) >= 0) { + iface->flags = convertFlags(req.ifr_flags); + } + +#ifdef SIOCGIFHWADDR + // Get the HW address + if (qt_safe_ioctl(socket, SIOCGIFHWADDR, &req) >= 0) { + uchar *addr = (uchar *)&req.ifr_addr; + iface->hardwareAddress = iface->makeHwAddress(6, addr); + } +#endif + } + + return iface; +} + +static QList<QNetworkInterfacePrivate *> interfaceListing() +{ + QList<QNetworkInterfacePrivate *> interfaces; + + int socket; + if ((socket = qt_safe_socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == -1) + return interfaces; // error + + QSet<QByteArray> names = interfaceNames(socket); + QSet<QByteArray>::ConstIterator it = names.constBegin(); + for ( ; it != names.constEnd(); ++it) { + ifreq req; + memset(&req, 0, sizeof(ifreq)); + memcpy(req.ifr_name, *it, qMin<int>(it->length() + 1, sizeof(req.ifr_name) - 1)); + + QNetworkInterfacePrivate *iface = findInterface(socket, interfaces, req); + + // Get the interface broadcast address + QNetworkAddressEntry entry; + if (iface->flags & QNetworkInterface::CanBroadcast) { + if (qt_safe_ioctl(socket, SIOCGIFBRDADDR, &req) >= 0) { + sockaddr *sa = &req.ifr_addr; + if (sa->sa_family == AF_INET) + entry.setBroadcast(addressFromSockaddr(sa)); + } + } + + // Get the interface netmask + if (qt_safe_ioctl(socket, SIOCGIFNETMASK, &req) >= 0) { + sockaddr *sa = &req.ifr_addr; + entry.setNetmask(addressFromSockaddr(sa)); + } + + // Get the address of the interface + if (qt_safe_ioctl(socket, SIOCGIFADDR, &req) >= 0) { + sockaddr *sa = &req.ifr_addr; + entry.setIp(addressFromSockaddr(sa)); + } + + iface->addressEntries << entry; + } + + ::close(socket); + return interfaces; +} + +#else +// use getifaddrs + +// platform-specific defs: +# ifdef Q_OS_LINUX +QT_BEGIN_INCLUDE_NAMESPACE +# include <features.h> +QT_END_INCLUDE_NAMESPACE +# endif + +# if defined(Q_OS_LINUX) && __GLIBC__ - 0 >= 2 && __GLIBC_MINOR__ - 0 >= 1 +# include <netpacket/packet.h> + +static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList) +{ + QList<QNetworkInterfacePrivate *> interfaces; + + for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) { + if ( !ptr->ifa_addr ) + continue; + + // Get the interface index + int ifindex = if_nametoindex(ptr->ifa_name); + + // on Linux we use AF_PACKET and sockaddr_ll to obtain hHwAddress + QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin(); + for ( ; if_it != interfaces.end(); ++if_it) + if ((*if_it)->index == ifindex) { + // this one has been added already + if ( ptr->ifa_addr->sa_family == AF_PACKET + && (*if_it)->hardwareAddress.isEmpty()) { + sockaddr_ll *sll = (sockaddr_ll *)ptr->ifa_addr; + (*if_it)->hardwareAddress = (*if_it)->makeHwAddress(sll->sll_halen, (uchar*)sll->sll_addr); + } + break; + } + if ( if_it != interfaces.end() ) + continue; + + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + iface->index = ifindex; + iface->name = QString::fromLatin1(ptr->ifa_name); + iface->flags = convertFlags(ptr->ifa_flags); + + if ( ptr->ifa_addr->sa_family == AF_PACKET ) { + sockaddr_ll *sll = (sockaddr_ll *)ptr->ifa_addr; + iface->hardwareAddress = iface->makeHwAddress(sll->sll_halen, (uchar*)sll->sll_addr); + } + } + + return interfaces; +} + +# elif defined(Q_OS_BSD4) +QT_BEGIN_INCLUDE_NAMESPACE +# include <net/if_dl.h> +QT_END_INCLUDE_NAMESPACE + +static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList) +{ + QList<QNetworkInterfacePrivate *> interfaces; + + // on NetBSD we use AF_LINK and sockaddr_dl + // scan the list for that family + for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) + if (ptr->ifa_addr && ptr->ifa_addr->sa_family == AF_LINK) { + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + + sockaddr_dl *sdl = (sockaddr_dl *)ptr->ifa_addr; + iface->index = sdl->sdl_index; + iface->name = QString::fromLatin1(ptr->ifa_name); + iface->flags = convertFlags(ptr->ifa_flags); + iface->hardwareAddress = iface->makeHwAddress(sdl->sdl_alen, (uchar*)LLADDR(sdl)); + } + + return interfaces; +} + +# else // Generic version + +static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList) +{ + QList<QNetworkInterfacePrivate *> interfaces; + + // make sure there's one entry for each interface + for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) { + // Get the interface index + int ifindex = if_nametoindex(ptr->ifa_name); + + QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin(); + for ( ; if_it != interfaces.end(); ++if_it) + if ((*if_it)->index == ifindex) + // this one has been added already + break; + + if (if_it == interfaces.end()) { + // none found, create + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + + iface->index = ifindex; + iface->name = QString::fromLatin1(ptr->ifa_name); + iface->flags = convertFlags(ptr->ifa_flags); + } + } + + return interfaces; +} + +# endif + + +static QList<QNetworkInterfacePrivate *> interfaceListing() +{ + QList<QNetworkInterfacePrivate *> interfaces; + + int socket; + if ((socket = qt_safe_socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == -1) + return interfaces; // error + + ifaddrs *interfaceListing; + if (getifaddrs(&interfaceListing) == -1) { + // error + ::close(socket); + return interfaces; + } + + interfaces = createInterfaces(interfaceListing); + for (ifaddrs *ptr = interfaceListing; ptr; ptr = ptr->ifa_next) { + // Get the interface index + int ifindex = if_nametoindex(ptr->ifa_name); + QNetworkInterfacePrivate *iface = 0; + QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin(); + for ( ; if_it != interfaces.end(); ++if_it) + if ((*if_it)->index == ifindex) { + // found this interface already + iface = *if_it; + break; + } + if (!iface) { + // skip all non-IP interfaces + continue; + } + + QNetworkAddressEntry entry; + entry.setIp(addressFromSockaddr(ptr->ifa_addr)); + if (entry.ip().isNull()) + // could not parse the address + continue; + + entry.setNetmask(addressFromSockaddr(ptr->ifa_netmask)); + if (iface->flags & QNetworkInterface::CanBroadcast) + entry.setBroadcast(addressFromSockaddr(ptr->ifa_broadaddr)); + + iface->addressEntries << entry; + } + + freeifaddrs(interfaceListing); + ::close(socket); + return interfaces; +} +#endif + +QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() +{ + return interfaceListing(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE diff --git a/src/network/kernel/qnetworkinterface_win.cpp b/src/network/kernel/qnetworkinterface_win.cpp new file mode 100644 index 0000000000..e83324c81f --- /dev/null +++ b/src/network/kernel/qnetworkinterface_win.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** 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 "qnetworkinterface.h" +#include "qnetworkinterface_p.h" + +#ifndef QT_NO_NETWORKINTERFACE + +#include "qnetworkinterface_win_p.h" +#include <qhostinfo.h> +#include <qhash.h> +#include <qurl.h> +#include <private/qsystemlibrary_p.h> + +QT_BEGIN_NAMESPACE + +typedef DWORD (WINAPI *PtrGetAdaptersInfo)(PIP_ADAPTER_INFO, PULONG); +static PtrGetAdaptersInfo ptrGetAdaptersInfo = 0; +typedef ULONG (WINAPI *PtrGetAdaptersAddresses)(ULONG, ULONG, PVOID, PIP_ADAPTER_ADDRESSES, PULONG); +static PtrGetAdaptersAddresses ptrGetAdaptersAddresses = 0; +typedef DWORD (WINAPI *PtrGetNetworkParams)(PFIXED_INFO, PULONG); +static PtrGetNetworkParams ptrGetNetworkParams = 0; + +static void resolveLibs() +{ + // try to find the functions we need from Iphlpapi.dll + static bool done = false; + + if (!done) { + done = true; + + HINSTANCE iphlpapiHnd = QSystemLibrary::load(L"iphlpapi"); + if (iphlpapiHnd == NULL) + return; + +#if defined(Q_OS_WINCE) + ptrGetAdaptersInfo = (PtrGetAdaptersInfo)GetProcAddress(iphlpapiHnd, L"GetAdaptersInfo"); + ptrGetAdaptersAddresses = (PtrGetAdaptersAddresses)GetProcAddress(iphlpapiHnd, L"GetAdaptersAddresses"); + ptrGetNetworkParams = (PtrGetNetworkParams)GetProcAddress(iphlpapiHnd, L"GetNetworkParams"); +#else + ptrGetAdaptersInfo = (PtrGetAdaptersInfo)GetProcAddress(iphlpapiHnd, "GetAdaptersInfo"); + ptrGetAdaptersAddresses = (PtrGetAdaptersAddresses)GetProcAddress(iphlpapiHnd, "GetAdaptersAddresses"); + ptrGetNetworkParams = (PtrGetNetworkParams)GetProcAddress(iphlpapiHnd, "GetNetworkParams"); +#endif + } +} + +static QHostAddress addressFromSockaddr(sockaddr *sa) +{ + QHostAddress address; + if (!sa) + return address; + + if (sa->sa_family == AF_INET) + address.setAddress(htonl(((sockaddr_in *)sa)->sin_addr.s_addr)); + else if (sa->sa_family == AF_INET6) + address.setAddress(((qt_sockaddr_in6 *)sa)->sin6_addr.qt_s6_addr); + else + qWarning("Got unknown socket family %d", sa->sa_family); + return address; + +} + +static QHash<QHostAddress, QHostAddress> ipv4Netmasks() +{ + //Retrieve all the IPV4 addresses & netmasks + IP_ADAPTER_INFO staticBuf[2]; // 2 is arbitrary + PIP_ADAPTER_INFO pAdapter = staticBuf; + ULONG bufSize = sizeof staticBuf; + QHash<QHostAddress, QHostAddress> ipv4netmasks; + + DWORD retval = ptrGetAdaptersInfo(pAdapter, &bufSize); + if (retval == ERROR_BUFFER_OVERFLOW) { + // need more memory + pAdapter = (IP_ADAPTER_INFO *)qMalloc(bufSize); + if (!pAdapter) + return ipv4netmasks; + // try again + if (ptrGetAdaptersInfo(pAdapter, &bufSize) != ERROR_SUCCESS) { + qFree(pAdapter); + return ipv4netmasks; + } + } else if (retval != ERROR_SUCCESS) { + // error + return ipv4netmasks; + } + + // iterate over the list and add the entries to our listing + for (PIP_ADAPTER_INFO ptr = pAdapter; ptr; ptr = ptr->Next) { + for (PIP_ADDR_STRING addr = &ptr->IpAddressList; addr; addr = addr->Next) { + QHostAddress address(QLatin1String(addr->IpAddress.String)); + QHostAddress mask(QLatin1String(addr->IpMask.String)); + ipv4netmasks[address] = mask; + } + } + if (pAdapter != staticBuf) + qFree(pAdapter); + + return ipv4netmasks; + +} + +static QList<QNetworkInterfacePrivate *> interfaceListingWinXP() +{ + QList<QNetworkInterfacePrivate *> interfaces; + IP_ADAPTER_ADDRESSES staticBuf[2]; // 2 is arbitrary + PIP_ADAPTER_ADDRESSES pAdapter = staticBuf; + ULONG bufSize = sizeof staticBuf; + + const QHash<QHostAddress, QHostAddress> &ipv4netmasks = ipv4Netmasks(); + ULONG flags = GAA_FLAG_INCLUDE_ALL_INTERFACES | + GAA_FLAG_INCLUDE_PREFIX | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_MULTICAST; + ULONG retval = ptrGetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAdapter, &bufSize); + if (retval == ERROR_BUFFER_OVERFLOW) { + // need more memory + pAdapter = (IP_ADAPTER_ADDRESSES *)qMalloc(bufSize); + if (!pAdapter) + return interfaces; + // try again + if (ptrGetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAdapter, &bufSize) != ERROR_SUCCESS) { + qFree(pAdapter); + return interfaces; + } + } else if (retval != ERROR_SUCCESS) { + // error + return interfaces; + } + + // iterate over the list and add the entries to our listing + for (PIP_ADAPTER_ADDRESSES ptr = pAdapter; ptr; ptr = ptr->Next) { + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + + iface->index = 0; + if (ptr->Length >= offsetof(IP_ADAPTER_ADDRESSES, Ipv6IfIndex) && ptr->Ipv6IfIndex != 0) + iface->index = ptr->Ipv6IfIndex; + else if (ptr->IfIndex != 0) + iface->index = ptr->IfIndex; + + iface->flags = QNetworkInterface::CanBroadcast; + if (ptr->OperStatus == IfOperStatusUp) + iface->flags |= QNetworkInterface::IsUp | QNetworkInterface::IsRunning; + if ((ptr->Flags & IP_ADAPTER_NO_MULTICAST) == 0) + iface->flags |= QNetworkInterface::CanMulticast; + + iface->name = QString::fromLocal8Bit(ptr->AdapterName); + iface->friendlyName = QString::fromWCharArray(ptr->FriendlyName); + if (ptr->PhysicalAddressLength) + iface->hardwareAddress = iface->makeHwAddress(ptr->PhysicalAddressLength, + ptr->PhysicalAddress); + else + // loopback if it has no address + iface->flags |= QNetworkInterface::IsLoopBack; + + // The GetAdaptersAddresses call has an interesting semantic: + // It can return a number N of addresses and a number M of prefixes. + // But if you have IPv6 addresses, generally N > M. + // I cannot find a way to relate the Address to the Prefix, aside from stopping + // the iteration at the last Prefix entry and assume that it applies to all addresses + // from that point on. + PIP_ADAPTER_PREFIX pprefix = 0; + if (ptr->Length >= offsetof(IP_ADAPTER_ADDRESSES, FirstPrefix)) + pprefix = ptr->FirstPrefix; + for (PIP_ADAPTER_UNICAST_ADDRESS addr = ptr->FirstUnicastAddress; addr; addr = addr->Next) { + QNetworkAddressEntry entry; + entry.setIp(addressFromSockaddr(addr->Address.lpSockaddr)); + if (pprefix) { + if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { + entry.setNetmask(ipv4netmasks[entry.ip()]); + + // broadcast address is set on postProcess() + } else { //IPV6 + entry.setPrefixLength(pprefix->PrefixLength); + } + pprefix = pprefix->Next ? pprefix->Next : pprefix; + } + iface->addressEntries << entry; + } + } + + if (pAdapter != staticBuf) + qFree(pAdapter); + + return interfaces; +} + +static QList<QNetworkInterfacePrivate *> interfaceListingWin2k() +{ + QList<QNetworkInterfacePrivate *> interfaces; + IP_ADAPTER_INFO staticBuf[2]; // 2 is arbitrary + PIP_ADAPTER_INFO pAdapter = staticBuf; + ULONG bufSize = sizeof staticBuf; + + DWORD retval = ptrGetAdaptersInfo(pAdapter, &bufSize); + if (retval == ERROR_BUFFER_OVERFLOW) { + // need more memory + pAdapter = (IP_ADAPTER_INFO *)qMalloc(bufSize); + if (!pAdapter) + return interfaces; + // try again + if (ptrGetAdaptersInfo(pAdapter, &bufSize) != ERROR_SUCCESS) { + qFree(pAdapter); + return interfaces; + } + } else if (retval != ERROR_SUCCESS) { + // error + return interfaces; + } + + // iterate over the list and add the entries to our listing + for (PIP_ADAPTER_INFO ptr = pAdapter; ptr; ptr = ptr->Next) { + QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate; + interfaces << iface; + + iface->index = ptr->Index; + iface->flags = QNetworkInterface::IsUp | QNetworkInterface::IsRunning; + if (ptr->Type == MIB_IF_TYPE_PPP) + iface->flags |= QNetworkInterface::IsPointToPoint; + else + iface->flags |= QNetworkInterface::CanBroadcast; + iface->name = QString::fromLocal8Bit(ptr->AdapterName); + iface->hardwareAddress = QNetworkInterfacePrivate::makeHwAddress(ptr->AddressLength, + ptr->Address); + + for (PIP_ADDR_STRING addr = &ptr->IpAddressList; addr; addr = addr->Next) { + QNetworkAddressEntry entry; + entry.setIp(QHostAddress(QLatin1String(addr->IpAddress.String))); + entry.setNetmask(QHostAddress(QLatin1String(addr->IpMask.String))); + // broadcast address is set on postProcess() + + iface->addressEntries << entry; + } + } + + if (pAdapter != staticBuf) + qFree(pAdapter); + + return interfaces; +} + +static QList<QNetworkInterfacePrivate *> interfaceListing() +{ + resolveLibs(); + if (ptrGetAdaptersAddresses != NULL) + return interfaceListingWinXP(); + else if (ptrGetAdaptersInfo != NULL) + return interfaceListingWin2k(); + + // failed + return QList<QNetworkInterfacePrivate *>(); +} + +QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() +{ + return interfaceListing(); +} + +QString QHostInfo::localDomainName() +{ + resolveLibs(); + if (ptrGetNetworkParams == NULL) + return QString(); // couldn't resolve + + FIXED_INFO info, *pinfo; + ULONG bufSize = sizeof info; + pinfo = &info; + if (ptrGetNetworkParams(pinfo, &bufSize) == ERROR_BUFFER_OVERFLOW) { + pinfo = (FIXED_INFO *)qMalloc(bufSize); + if (!pinfo) + return QString(); + // try again + if (ptrGetNetworkParams(pinfo, &bufSize) != ERROR_SUCCESS) { + qFree(pinfo); + return QString(); // error + } + } + + QString domainName = QUrl::fromAce(pinfo->DomainName); + + if (pinfo != &info) + qFree(pinfo); + + return domainName; +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE diff --git a/src/network/kernel/qnetworkinterface_win_p.h b/src/network/kernel/qnetworkinterface_win_p.h new file mode 100644 index 0000000000..ca15406012 --- /dev/null +++ b/src/network/kernel/qnetworkinterface_win_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 QNETWORKINTERFACE_WIN_P_H +#define QNETWORKINTERFACE_WIN_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <winsock2.h> +#include <qt_windows.h> +#include <time.h> + +QT_BEGIN_NAMESPACE + +#ifndef GAA_FLAG_INCLUDE_ALL_INTERFACES +# define GAA_FLAG_INCLUDE_ALL_INTERFACES 0x0100 +#endif +#ifndef MAX_ADAPTER_ADDRESS_LENGTH +// definitions from iptypes.h +# define MAX_ADAPTER_DESCRIPTION_LENGTH 128 // arb. +# define MAX_ADAPTER_NAME_LENGTH 256 // arb. +# define MAX_ADAPTER_ADDRESS_LENGTH 8 // arb. +# define DEFAULT_MINIMUM_ENTITIES 32 // arb. +# define MAX_HOSTNAME_LEN 128 // arb. +# define MAX_DOMAIN_NAME_LEN 128 // arb. +# define MAX_SCOPE_ID_LEN 256 // arb. + +# define GAA_FLAG_SKIP_UNICAST 0x0001 +# define GAA_FLAG_SKIP_ANYCAST 0x0002 +# define GAA_FLAG_SKIP_MULTICAST 0x0004 +# define GAA_FLAG_SKIP_DNS_SERVER 0x0008 +# define GAA_FLAG_INCLUDE_PREFIX 0x0010 +# define GAA_FLAG_SKIP_FRIENDLY_NAME 0x0020 + +# define IP_ADAPTER_DDNS_ENABLED 0x01 +# define IP_ADAPTER_REGISTER_ADAPTER_SUFFIX 0x02 +# define IP_ADAPTER_DHCP_ENABLED 0x04 +# define IP_ADAPTER_RECEIVE_ONLY 0x08 +# define IP_ADAPTER_NO_MULTICAST 0x10 +# define IP_ADAPTER_IPV6_OTHER_STATEFUL_CONFIG 0x20 + +# define MIB_IF_TYPE_OTHER 1 +# define MIB_IF_TYPE_ETHERNET 6 +# define MIB_IF_TYPE_TOKENRING 9 +# define MIB_IF_TYPE_FDDI 15 +# define MIB_IF_TYPE_PPP 23 +# define MIB_IF_TYPE_LOOPBACK 24 +# define MIB_IF_TYPE_SLIP 28 + +#endif +// copied from qnativesocketengine_win.cpp +struct qt_in6_addr { + u_char qt_s6_addr[16]; +}; +typedef struct { + short sin6_family; /* AF_INET6 */ + u_short sin6_port; /* Transport level port number */ + u_long sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + u_long sin6_scope_id; /* set of interfaces for a scope */ +} qt_sockaddr_in6; + +// copied from MSDN online help +typedef enum { + IpPrefixOriginOther = 0, + IpPrefixOriginManual, + IpPrefixOriginWellKnown, + IpPrefixOriginDhcp, + IpPrefixOriginRouterAdvertisement +} IP_PREFIX_ORIGIN; + +typedef enum { + IpSuffixOriginOther = 0, + IpSuffixOriginManual, + IpSuffixOriginWellKnown, + IpSuffixOriginDhcp, + IpSuffixOriginLinkLayerAddress, + IpSuffixOriginRandom +} IP_SUFFIX_ORIGIN; + +typedef enum { + IpDadStateInvalid = 0, + IpDadStateTentative, + IpDadStateDuplicate, + IpDadStateDeprecated, + IpDadStatePreferred, +} IP_DAD_STATE; + +typedef enum { + IfOperStatusUp = 1, + IfOperStatusDown, + IfOperStatusTesting, + IfOperStatusUnknown, + IfOperStatusDormant, + IfOperStatusNotPresent, + IfOperStatusLowerLayerDown +} IF_OPER_STATUS; + +typedef struct _IP_ADAPTER_UNICAST_ADDRESS { + union { + ULONGLONG Alignment; + struct { + ULONG Length; + DWORD Flags; + }; + }; + struct _IP_ADAPTER_UNICAST_ADDRESS* Next; + SOCKET_ADDRESS Address; + IP_PREFIX_ORIGIN PrefixOrigin; + IP_SUFFIX_ORIGIN SuffixOrigin; + IP_DAD_STATE DadState; + ULONG ValidLifetime; + ULONG PreferredLifetime; + ULONG LeaseLifetime; +} IP_ADAPTER_UNICAST_ADDRESS, *PIP_ADAPTER_UNICAST_ADDRESS; + +typedef struct _IP_ADAPTER_ANYCAST_ADDRESS + IP_ADAPTER_ANYCAST_ADDRESS, *PIP_ADAPTER_ANYCAST_ADDRESS; + +typedef struct _IP_ADAPTER_MULTICAST_ADDRESS + IP_ADAPTER_MULTICAST_ADDRESS, + *PIP_ADAPTER_MULTICAST_ADDRESS; + +typedef struct _IP_ADAPTER_DNS_SERVER_ADDRESS + IP_ADAPTER_DNS_SERVER_ADDRESS, + *PIP_ADAPTER_DNS_SERVER_ADDRESS; + +typedef struct _IP_ADAPTER_PREFIX { + union { + ULONGLONG Alignment; + struct { + ULONG Length; + DWORD Flags; + }; + }; + struct _IP_ADAPTER_PREFIX* Next; + SOCKET_ADDRESS Address; + ULONG PrefixLength; +} IP_ADAPTER_PREFIX, + *PIP_ADAPTER_PREFIX; + +typedef struct _IP_ADAPTER_ADDRESSES { + union { + ULONGLONG Alignment; + struct { + ULONG Length; + DWORD IfIndex; + }; + }; + struct _IP_ADAPTER_ADDRESSES* Next; + PCHAR AdapterName; + PIP_ADAPTER_UNICAST_ADDRESS FirstUnicastAddress; + PIP_ADAPTER_ANYCAST_ADDRESS FirstAnycastAddress; + PIP_ADAPTER_MULTICAST_ADDRESS FirstMulticastAddress; + PIP_ADAPTER_DNS_SERVER_ADDRESS FirstDnsServerAddress; + PWCHAR DnsSuffix; + PWCHAR Description; + PWCHAR FriendlyName; + BYTE PhysicalAddress[MAX_ADAPTER_ADDRESS_LENGTH]; + DWORD PhysicalAddressLength; + DWORD Flags; + DWORD Mtu; + DWORD IfType; + IF_OPER_STATUS OperStatus; + DWORD Ipv6IfIndex; + DWORD ZoneIndices[16]; + PIP_ADAPTER_PREFIX FirstPrefix; +} IP_ADAPTER_ADDRESSES, + *PIP_ADAPTER_ADDRESSES; + +typedef struct { + char String[4 * 4]; +} IP_ADDRESS_STRING, *PIP_ADDRESS_STRING, IP_MASK_STRING, *PIP_MASK_STRING; + +typedef struct _IP_ADDR_STRING { + struct _IP_ADDR_STRING* Next; + IP_ADDRESS_STRING IpAddress; + IP_MASK_STRING IpMask; + DWORD Context; +} IP_ADDR_STRING, + *PIP_ADDR_STRING; + +typedef struct _IP_ADAPTER_INFO { + struct _IP_ADAPTER_INFO* Next; + DWORD ComboIndex; + char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; + char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4]; + UINT AddressLength; + BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; + DWORD Index; + UINT Type; + UINT DhcpEnabled; + PIP_ADDR_STRING CurrentIpAddress; + IP_ADDR_STRING IpAddressList; + IP_ADDR_STRING GatewayList; + IP_ADDR_STRING DhcpServer; + BOOL HaveWins; + IP_ADDR_STRING PrimaryWinsServer; + IP_ADDR_STRING SecondaryWinsServer; + time_t LeaseObtained; + time_t LeaseExpires; +} IP_ADAPTER_INFO, + *PIP_ADAPTER_INFO; + +typedef struct { + char HostName[MAX_HOSTNAME_LEN + 4]; + char DomainName[MAX_DOMAIN_NAME_LEN + 4]; + PIP_ADDR_STRING CurrentDnsServer; + IP_ADDR_STRING DnsServerList; + UINT NodeType; + char ScopeId[MAX_SCOPE_ID_LEN + 4]; + UINT EnableRouting; + UINT EnableProxy; + UINT EnableDns; +} FIXED_INFO, *PFIXED_INFO; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp new file mode 100644 index 0000000000..68ff95543a --- /dev/null +++ b/src/network/kernel/qnetworkproxy.cpp @@ -0,0 +1,1310 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +/*! + \class QNetworkProxy + + \since 4.1 + + \brief The QNetworkProxy class provides a network layer proxy. + + \reentrant + \ingroup network + \inmodule QtNetwork + + QNetworkProxy provides the method for configuring network layer + proxy support to the Qt network classes. The currently supported + classes are QAbstractSocket, QTcpSocket, QUdpSocket, QTcpServer, + QNetworkAccessManager and QFtp. The proxy support is designed to + be as transparent as possible. This means that existing + network-enabled applications that you have written should + automatically support network proxy using the following code. + + \snippet doc/src/snippets/code/src_network_kernel_qnetworkproxy.cpp 0 + + An alternative to setting an application wide proxy is to specify + the proxy for individual sockets using QAbstractSocket::setProxy() + and QTcpServer::setProxy(). In this way, it is possible to disable + the use of a proxy for specific sockets using the following code: + + \snippet doc/src/snippets/code/src_network_kernel_qnetworkproxy.cpp 1 + + Network proxy is not used if the address used in \l + {QAbstractSocket::connectToHost()}{connectToHost()}, \l + {QUdpSocket::bind()}{bind()} or \l + {QTcpServer::listen()}{listen()} is equivalent to + QHostAddress::LocalHost or QHostAddress::LocalHostIPv6. + + Each type of proxy support has certain restrictions associated with it. + You should read the \l{ProxyType} documentation carefully before + selecting a proxy type to use. + + \note Changes made to currently connected sockets do not take effect. + If you need to change a connected socket, you should reconnect it. + + \section1 SOCKS5 + + The SOCKS5 support in Qt 4 is based on \l{RFC 1928} and \l{RFC 1929}. + The supported authentication methods are no authentication and + username/password authentication. Both IPv4 and IPv6 are + supported. Domain names are resolved through the SOCKS5 server if + the QNetworkProxy::HostNameLookupCapability is enabled, otherwise + they are resolved locally and the IP address is sent to the + server. There are several things to remember when using SOCKS5 + with QUdpSocket and QTcpServer: + + With QUdpSocket, a call to \l {QUdpSocket::bind()}{bind()} may fail + with a timeout error. If a port number other than 0 is passed to + \l {QUdpSocket::bind()}{bind()}, it is not guaranteed that it is the + specified port that will be used. + Use \l{QUdpSocket::localPort()}{localPort()} and + \l{QUdpSocket::localAddress()}{localAddress()} to get the actual + address and port number in use. Because proxied UDP goes through + two UDP connections, it is more likely that packets will be dropped. + + With QTcpServer a call to \l{QTcpServer::listen()}{listen()} may + fail with a timeout error. If a port number other than 0 is passed + to \l{QTcpServer::listen()}{listen()}, then it is not guaranteed + that it is the specified port that will be used. + Use \l{QTcpServer::serverPort()}{serverPort()} and + \l{QTcpServer::serverAddress()}{serverAddress()} to get the actual + address and port used to listen for connections. SOCKS5 only supports + one accepted connection per call to \l{QTcpServer::listen()}{listen()}, + and each call is likely to result in a different + \l{QTcpServer::serverPort()}{serverPort()} being used. + + \sa QAbstractSocket, QTcpServer +*/ + +/*! + \enum QNetworkProxy::ProxyType + + This enum describes the types of network proxying provided in Qt. + + There are two types of proxies that Qt understands: + transparent proxies and caching proxies. The first group consists + of proxies that can handle any arbitrary data transfer, while the + second can only handle specific requests. The caching proxies only + make sense for the specific classes where they can be used. + + \value NoProxy No proxying is used + \value DefaultProxy Proxy is determined based on the application proxy set using setApplicationProxy() + \value Socks5Proxy \l Socks5 proxying is used + \value HttpProxy HTTP transparent proxying is used + \value HttpCachingProxy Proxying for HTTP requests only + \value FtpCachingProxy Proxying for FTP requests only + + The table below lists different proxy types and their + capabilities. Since each proxy type has different capabilities, it + is important to understand them before choosing a proxy type. + + \table + \header + \o Proxy type + \o Description + \o Default capabilities + + \row + \o SOCKS 5 + \o Generic proxy for any kind of connection. Supports TCP, + UDP, binding to a port (incoming connections) and + authentication. + \o TunnelingCapability, ListeningCapability, + UdpTunnelingCapability, HostNameLookupCapability + + \row + \o HTTP + \o Implemented using the "CONNECT" command, supports only + outgoing TCP connections; supports authentication. + \o TunnelingCapability, CachingCapability, HostNameLookupCapability + + \row + \o Caching-only HTTP + \o Implemented using normal HTTP commands, it is useful only + in the context of HTTP requests (see QNetworkAccessManager) + \o CachingCapability, HostNameLookupCapability + + \row + \o Caching FTP + \o Implemented using an FTP proxy, it is useful only in the + context of FTP requests (see QFtp, + QNetworkAccessManager) + \o CachingCapability, HostNameLookupCapability + + \endtable + + Also note that you shouldn't set the application default proxy + (setApplicationProxy()) to a proxy that doesn't have the + TunnelingCapability capability. If you do, QTcpSocket will not + know how to open connections. + + \sa setType(), type(), capabilities(), setCapabilities() +*/ + +/*! + \enum QNetworkProxy::Capability + \since 4.5 + + These flags indicate the capabilities that a given proxy server + supports. + + QNetworkProxy sets different capabilities by default when the + object is created (see QNetworkProxy::ProxyType for a list of the + defaults). However, it is possible to change the capabitilies + after the object has been created with setCapabilities(). + + The capabilities that QNetworkProxy supports are: + + \value TunnelingCapability Ability to open transparent, tunneled + TCP connections to a remote host. The proxy server relays the + transmission verbatim from one side to the other and does no + caching. + + \value ListeningCapability Ability to create a listening socket + and wait for an incoming TCP connection from a remote host. + + \value UdpTunnelingCapability Ability to relay UDP datagrams via + the proxy server to and from a remote host. + + \value CachingCapability Ability to cache the contents of the + transfer. This capability is specific to each protocol and proxy + type. For example, HTTP proxies can cache the contents of web data + transferred with "GET" commands. + + \value HostNameLookupCapability Ability to connect to perform the + lookup on a remote host name and connect to it, as opposed to + requiring the application to perform the name lookup and request + connection to IP addresses only. +*/ + +#include "qnetworkproxy.h" + +#ifndef QT_NO_NETWORKPROXY + +#include "private/qnetworkproxy_p.h" +#include "private/qsocks5socketengine_p.h" +#include "private/qhttpsocketengine_p.h" +#include "qauthenticator.h" +#include "qhash.h" +#include "qmutex.h" +#include "qurl.h" + +QT_BEGIN_NAMESPACE + +class QSocks5SocketEngineHandler; +class QHttpSocketEngineHandler; + +class QGlobalNetworkProxy +{ +public: + QGlobalNetworkProxy() + : mutex(QMutex::Recursive) + , applicationLevelProxy(0) + , applicationLevelProxyFactory(0) + , socks5SocketEngineHandler(0) + , httpSocketEngineHandler(0) + { + } + + ~QGlobalNetworkProxy() + { + delete applicationLevelProxy; + delete applicationLevelProxyFactory; + delete socks5SocketEngineHandler; + delete httpSocketEngineHandler; + } + + void init() + { + QMutexLocker lock(&mutex); +#ifndef QT_NO_SOCKS5 + if (!socks5SocketEngineHandler) + socks5SocketEngineHandler = new QSocks5SocketEngineHandler(); +#endif +#ifndef QT_NO_HTTP + if (!httpSocketEngineHandler) + httpSocketEngineHandler = new QHttpSocketEngineHandler(); +#endif + } + + void setApplicationProxy(const QNetworkProxy &proxy) + { + QMutexLocker lock(&mutex); + if (!applicationLevelProxy) + applicationLevelProxy = new QNetworkProxy; + *applicationLevelProxy = proxy; + delete applicationLevelProxyFactory; + applicationLevelProxyFactory = 0; + } + + void setApplicationProxyFactory(QNetworkProxyFactory *factory) + { + QMutexLocker lock(&mutex); + if (applicationLevelProxy) + *applicationLevelProxy = QNetworkProxy(); + delete applicationLevelProxyFactory; + applicationLevelProxyFactory = factory; + } + + QNetworkProxy applicationProxy() + { + return proxyForQuery(QNetworkProxyQuery()).first(); + } + + QList<QNetworkProxy> proxyForQuery(const QNetworkProxyQuery &query); + +private: + QMutex mutex; + QNetworkProxy *applicationLevelProxy; + QNetworkProxyFactory *applicationLevelProxyFactory; + QSocks5SocketEngineHandler *socks5SocketEngineHandler; + QHttpSocketEngineHandler *httpSocketEngineHandler; +}; + +QList<QNetworkProxy> QGlobalNetworkProxy::proxyForQuery(const QNetworkProxyQuery &query) +{ + QMutexLocker locker(&mutex); + + QList<QNetworkProxy> result; + if (!applicationLevelProxyFactory) { + if (applicationLevelProxy + && applicationLevelProxy->type() != QNetworkProxy::DefaultProxy) + result << *applicationLevelProxy; + else + result << QNetworkProxy(QNetworkProxy::NoProxy); + return result; + } + + // we have a factory + result = applicationLevelProxyFactory->queryProxy(query); + if (result.isEmpty()) { + qWarning("QNetworkProxyFactory: factory %p has returned an empty result set", + applicationLevelProxyFactory); + result << QNetworkProxy(QNetworkProxy::NoProxy); + } + return result; +} + +Q_GLOBAL_STATIC(QGlobalNetworkProxy, globalNetworkProxy) + +namespace { + template<bool> struct StaticAssertTest; + template<> struct StaticAssertTest<true> { enum { Value = 1 }; }; +} + +static inline void qt_noop_with_arg(int) {} +#define q_static_assert(expr) qt_noop_with_arg(sizeof(StaticAssertTest< expr >::Value)) + +static QNetworkProxy::Capabilities defaultCapabilitiesForType(QNetworkProxy::ProxyType type) +{ + q_static_assert(int(QNetworkProxy::DefaultProxy) == 0); + q_static_assert(int(QNetworkProxy::FtpCachingProxy) == 5); + static const int defaults[] = + { + /* [QNetworkProxy::DefaultProxy] = */ + (int(QNetworkProxy::ListeningCapability) | + int(QNetworkProxy::TunnelingCapability) | + int(QNetworkProxy::UdpTunnelingCapability)), + /* [QNetworkProxy::Socks5Proxy] = */ + (int(QNetworkProxy::TunnelingCapability) | + int(QNetworkProxy::ListeningCapability) | + int(QNetworkProxy::UdpTunnelingCapability) | + int(QNetworkProxy::HostNameLookupCapability)), + // it's weird to talk about the proxy capabilities of a "not proxy"... + /* [QNetworkProxy::NoProxy] = */ + (int(QNetworkProxy::ListeningCapability) | + int(QNetworkProxy::TunnelingCapability) | + int(QNetworkProxy::UdpTunnelingCapability)), + /* [QNetworkProxy::HttpProxy] = */ + (int(QNetworkProxy::TunnelingCapability) | + int(QNetworkProxy::CachingCapability) | + int(QNetworkProxy::HostNameLookupCapability)), + /* [QNetworkProxy::HttpCachingProxy] = */ + (int(QNetworkProxy::CachingCapability) | + int(QNetworkProxy::HostNameLookupCapability)), + /* [QNetworkProxy::FtpCachingProxy] = */ + (int(QNetworkProxy::CachingCapability) | + int(QNetworkProxy::HostNameLookupCapability)), + }; + + if (int(type) < 0 || int(type) > int(QNetworkProxy::FtpCachingProxy)) + type = QNetworkProxy::DefaultProxy; + return QNetworkProxy::Capabilities(defaults[int(type)]); +} + +class QNetworkProxyPrivate: public QSharedData +{ +public: + QString hostName; + QString user; + QString password; + QNetworkProxy::Capabilities capabilities; + quint16 port; + QNetworkProxy::ProxyType type; + bool capabilitiesSet; + + inline QNetworkProxyPrivate(QNetworkProxy::ProxyType t = QNetworkProxy::DefaultProxy, + const QString &h = QString(), quint16 p = 0, + const QString &u = QString(), const QString &pw = QString()) + : hostName(h), + user(u), + password(pw), + capabilities(defaultCapabilitiesForType(t)), + port(p), + type(t), + capabilitiesSet(false) + { } + + inline bool operator==(const QNetworkProxyPrivate &other) const + { + return type == other.type && + port == other.port && + hostName == other.hostName && + user == other.user && + password == other.password && + capabilities == other.capabilities; + } +}; + +template<> void QSharedDataPointer<QNetworkProxyPrivate>::detach() +{ + if (d && d->ref == 1) + return; + QNetworkProxyPrivate *x = (d ? new QNetworkProxyPrivate(*d) + : new QNetworkProxyPrivate); + x->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = x; +} + +/*! + Constructs a QNetworkProxy with DefaultProxy type; the proxy type is + determined by applicationProxy(), which defaults to NoProxy. + + \sa setType(), setApplicationProxy() +*/ +QNetworkProxy::QNetworkProxy() + : d(0) +{ + if (QGlobalNetworkProxy *globalProxy = globalNetworkProxy()) + globalProxy->init(); +} + +/*! + Constructs a QNetworkProxy with \a type, \a hostName, \a port, + \a user and \a password. + + The default capabilities for proxy type \a type are set automatically. + + \sa capabilities() +*/ +QNetworkProxy::QNetworkProxy(ProxyType type, const QString &hostName, quint16 port, + const QString &user, const QString &password) + : d(new QNetworkProxyPrivate(type, hostName, port, user, password)) +{ + if (QGlobalNetworkProxy *globalProxy = globalNetworkProxy()) + globalProxy->init(); +} + +/*! + Constructs a copy of \a other. +*/ +QNetworkProxy::QNetworkProxy(const QNetworkProxy &other) + : d(other.d) +{ +} + +/*! + Destroys the QNetworkProxy object. +*/ +QNetworkProxy::~QNetworkProxy() +{ + // QSharedDataPointer takes care of deleting for us +} + +/*! + \since 4.4 + + Compares the value of this network proxy to \a other and returns true + if they are equal (same proxy type, server as well as username and password) +*/ +bool QNetworkProxy::operator==(const QNetworkProxy &other) const +{ + return d == other.d || (d && other.d && *d == *other.d); +} + +/*! + \fn bool QNetworkProxy::operator!=(const QNetworkProxy &other) const + \since 4.4 + + Compares the value of this network proxy to \a other and returns true + if they differ. +\*/ + +/*! + \since 4.2 + + Assigns the value of the network proxy \a other to this network proxy. +*/ +QNetworkProxy &QNetworkProxy::operator=(const QNetworkProxy &other) +{ + d = other.d; + return *this; +} + +/*! + Sets the proxy type for this instance to be \a type. + + Note that changing the type of a proxy does not change + the set of capabilities this QNetworkProxy object holds if any + capabilities have been set with setCapabilities(). + + \sa type(), setCapabilities() +*/ +void QNetworkProxy::setType(QNetworkProxy::ProxyType type) +{ + d->type = type; + if (!d->capabilitiesSet) + d->capabilities = defaultCapabilitiesForType(type); +} + +/*! + Returns the proxy type for this instance. + + \sa setType() +*/ +QNetworkProxy::ProxyType QNetworkProxy::type() const +{ + return d ? d->type : DefaultProxy; +} + +/*! + \since 4.5 + + Sets the capabilities of this proxy to \a capabilities. + + \sa setType(), capabilities() +*/ +void QNetworkProxy::setCapabilities(Capabilities capabilities) +{ + d->capabilities = capabilities; + d->capabilitiesSet = true; +} + +/*! + \since 4.5 + + Returns the capabilities of this proxy server. + + \sa setCapabilities(), type() +*/ +QNetworkProxy::Capabilities QNetworkProxy::capabilities() const +{ + return d ? d->capabilities : defaultCapabilitiesForType(DefaultProxy); +} + +/*! + \since 4.4 + + Returns true if this proxy supports the + QNetworkProxy::CachingCapability capability. + + In Qt 4.4, the capability was tied to the proxy type, but since Qt + 4.5 it is possible to remove the capability of caching from a + proxy by calling setCapabilities(). + + \sa capabilities(), type(), isTransparentProxy() +*/ +bool QNetworkProxy::isCachingProxy() const +{ + return capabilities() & CachingCapability; +} + +/*! + \since 4.4 + + Returns true if this proxy supports transparent tunneling of TCP + connections. This matches the QNetworkProxy::TunnelingCapability + capability. + + In Qt 4.4, the capability was tied to the proxy type, but since Qt + 4.5 it is possible to remove the capability of caching from a + proxy by calling setCapabilities(). + + \sa capabilities(), type(), isCachingProxy() +*/ +bool QNetworkProxy::isTransparentProxy() const +{ + return capabilities() & TunnelingCapability; +} + +/*! + Sets the user name for proxy authentication to be \a user. + + \sa user(), setPassword(), password() +*/ +void QNetworkProxy::setUser(const QString &user) +{ + d->user = user; +} + +/*! + Returns the user name used for authentication. + + \sa setUser(), setPassword(), password() +*/ +QString QNetworkProxy::user() const +{ + return d ? d->user : QString(); +} + +/*! + Sets the password for proxy authentication to be \a password. + + \sa user(), setUser(), password() +*/ +void QNetworkProxy::setPassword(const QString &password) +{ + d->password = password; +} + +/*! + Returns the password used for authentication. + + \sa user(), setPassword(), setUser() +*/ +QString QNetworkProxy::password() const +{ + return d ? d->password : QString(); +} + +/*! + Sets the host name of the proxy host to be \a hostName. + + \sa hostName(), setPort(), port() +*/ +void QNetworkProxy::setHostName(const QString &hostName) +{ + d->hostName = hostName; +} + +/*! + Returns the host name of the proxy host. + + \sa setHostName(), setPort(), port() +*/ +QString QNetworkProxy::hostName() const +{ + return d ? d->hostName : QString(); +} + +/*! + Sets the port of the proxy host to be \a port. + + \sa hostName(), setHostName(), port() +*/ +void QNetworkProxy::setPort(quint16 port) +{ + d->port = port; +} + +/*! + Returns the port of the proxy host. + + \sa setHostName(), setPort(), hostName() +*/ +quint16 QNetworkProxy::port() const +{ + return d ? d->port : 0; +} + +/*! + Sets the application level network proxying to be \a networkProxy. + + If a QAbstractSocket or QTcpSocket has the + QNetworkProxy::DefaultProxy type, then the QNetworkProxy set with + this function is used. If you want more flexibility in determining + which the proxy, use the QNetworkProxyFactory class. + + Setting a default proxy value with this function will override the + application proxy factory set with + QNetworkProxyFactory::setApplicationProxyFactory. + + \sa QNetworkProxyFactory, applicationProxy(), QAbstractSocket::setProxy(), QTcpServer::setProxy() +*/ +void QNetworkProxy::setApplicationProxy(const QNetworkProxy &networkProxy) +{ + if (globalNetworkProxy()) { + // don't accept setting the proxy to DefaultProxy + if (networkProxy.type() == DefaultProxy) + globalNetworkProxy()->setApplicationProxy(QNetworkProxy::NoProxy); + else + globalNetworkProxy()->setApplicationProxy(networkProxy); + } +} + +/*! + Returns the application level network proxying. + + If a QAbstractSocket or QTcpSocket has the + QNetworkProxy::DefaultProxy type, then the QNetworkProxy returned + by this function is used. + + \sa QNetworkProxyFactory, setApplicationProxy(), QAbstractSocket::proxy(), QTcpServer::proxy() +*/ +QNetworkProxy QNetworkProxy::applicationProxy() +{ + if (globalNetworkProxy()) + return globalNetworkProxy()->applicationProxy(); + return QNetworkProxy(); +} + +class QNetworkProxyQueryPrivate: public QSharedData +{ +public: + inline QNetworkProxyQueryPrivate() + : localPort(-1), type(QNetworkProxyQuery::TcpSocket) + { } + + bool operator==(const QNetworkProxyQueryPrivate &other) const + { + return type == other.type && + localPort == other.localPort && + remote == other.remote; + } + + QUrl remote; + int localPort; + QNetworkProxyQuery::QueryType type; +}; + +template<> void QSharedDataPointer<QNetworkProxyQueryPrivate>::detach() +{ + if (d && d->ref == 1) + return; + QNetworkProxyQueryPrivate *x = (d ? new QNetworkProxyQueryPrivate(*d) + : new QNetworkProxyQueryPrivate); + x->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = x; +} + +/*! + \class QNetworkProxyQuery + \since 4.5 + \inmodule QtNetwork + \brief The QNetworkProxyQuery class is used to query the proxy + settings for a socket + + QNetworkProxyQuery holds the details of a socket being created or + request being made. It is used by QNetworkProxy and + QNetworkProxyFactory to allow applications to have a more + fine-grained control over which proxy servers are used, depending + on the details of the query. This allows an application to apply + different settings, according to the protocol or destination + hostname, for instance. + + QNetworkProxyQuery supports the following criteria for selecting + the proxy: + + \list + \o the type of query + \o the local port number to use + \o the destination host name + \o the destination port number + \o the protocol name, such as "http" or "ftp" + \o the URL being requested + \endlist + + The destination host name is the host in the connection in the + case of outgoing connection sockets. It is the \c hostName + parameter passed to QTcpSocket::connectToHost() or the host + component of a URL requested with QNetworkRequest. + + The destination port number is the requested port to connect to in + the case of outgoing sockets, while the local port number is the + port the socket wishes to use locally before attempting the + external connection. In most cases, the local port number is used + by listening sockets only (QTcpSocket) or by datagram sockets + (QUdpSocket). + + The protocol name is an arbitrary string that indicates the type + of connection being attempted. For example, it can match the + scheme of a URL, like "http", "https" and "ftp". In most cases, + the proxy selection will not change depending on the protocol, but + this information is provided in case a better choice can be made, + like choosing an caching HTTP proxy for HTTP-based connections, + but a more powerful SOCKSv5 proxy for all others. + + Some of the criteria may not make sense in all of the types of + query. The following table lists the criteria that are most + commonly used, according to the type of query. + + \table + \header + \o Query type + \o Description + + \row + \o TcpSocket + \o Normal sockets requesting a connection to a remote server, + like QTcpSocket. The peer hostname and peer port match the + values passed to QTcpSocket::connectToHost(). The local port + is usually -1, indicating the socket has no preference in + which port should be used. The URL component is not used. + + \row + \o UdpSocket + \o Datagram-based sockets, which can both send and + receive. The local port, remote host or remote port fields + can all be used or be left unused, depending on the + characteristics of the socket. The URL component is not used. + + \row + \o TcpServer + \o Passive server sockets that listen on a port and await + incoming connections from the network. Normally, only the + local port is used, but the remote address could be used in + specific circumstances, for example to indicate which remote + host a connection is expected from. The URL component is not used. + + \row + \o UrlRequest + \o A more high-level request, such as those coming from + QNetworkAccessManager. These requests will inevitably use an + outgoing TCP socket, but the this query type is provided to + indicate that more detailed information is present in the URL + component. For ease of implementation, the URL's host and + port are set as the destination address. + \endtable + + It should be noted that any of the criteria may be missing or + unknown (an empty QString for the hostname or protocol name, -1 + for the port numbers). If that happens, the functions executing + the query should make their best guess or apply some + implementation-defined default values. + + \sa QNetworkProxy, QNetworkProxyFactory, QNetworkAccessManager, + QAbstractSocket::setProxy() +*/ + +/*! + \enum QNetworkProxyQuery::QueryType + + Describes the type of one QNetworkProxyQuery query. + + \value TcpSocket a normal, outgoing TCP socket + \value UdpSocket a datagram-based UDP socket, which could send + to multiple destinations + \value TcpServer a TCP server that listens for incoming + connections from the network + \value UrlRequest a more complex request which involves loading + of a URL + + \sa queryType(), setQueryType() +*/ + +/*! + Constructs a default QNetworkProxyQuery object. By default, the + query type will be QNetworkProxyQuery::TcpSocket. +*/ +QNetworkProxyQuery::QNetworkProxyQuery() +{ +} + +/*! + Constructs a QNetworkProxyQuery with the URL \a requestUrl and + sets the query type to \a queryType. + + \sa protocolTag(), peerHostName(), peerPort() +*/ +QNetworkProxyQuery::QNetworkProxyQuery(const QUrl &requestUrl, QueryType queryType) +{ + d->remote = requestUrl; + d->type = queryType; +} + +/*! + Constructs a QNetworkProxyQuery of type \a queryType and sets the + protocol tag to be \a protocolTag. This constructor is suitable + for QNetworkProxyQuery::TcpSocket queries, because it sets the + peer hostname to \a hostname and the peer's port number to \a + port. +*/ +QNetworkProxyQuery::QNetworkProxyQuery(const QString &hostname, int port, + const QString &protocolTag, + QueryType queryType) +{ + d->remote.setScheme(protocolTag); + d->remote.setHost(hostname); + d->remote.setPort(port); + d->type = queryType; +} + +/*! + Constructs a QNetworkProxyQuery of type \a queryType and sets the + protocol tag to be \a protocolTag. This constructor is suitable + for QNetworkProxyQuery::TcpSocket queries because it sets the + local port number to \a bindPort. + + Note that \a bindPort is of type quint16 to indicate the exact + port number that is requested. The value of -1 (unknown) is not + allowed in this context. + + \sa localPort() +*/ +QNetworkProxyQuery::QNetworkProxyQuery(quint16 bindPort, const QString &protocolTag, + QueryType queryType) +{ + d->remote.setScheme(protocolTag); + d->localPort = bindPort; + d->type = queryType; +} + +/*! + Constructs a QNetworkProxyQuery object that is a copy of \a other. +*/ +QNetworkProxyQuery::QNetworkProxyQuery(const QNetworkProxyQuery &other) + : d(other.d) +{ +} + +/*! + Destroys this QNetworkProxyQuery object. +*/ +QNetworkProxyQuery::~QNetworkProxyQuery() +{ + // QSharedDataPointer automatically deletes +} + +/*! + Copies the contents of \a other. +*/ +QNetworkProxyQuery &QNetworkProxyQuery::operator=(const QNetworkProxyQuery &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this QNetworkProxyQuery object contains the same + data as \a other. +*/ +bool QNetworkProxyQuery::operator==(const QNetworkProxyQuery &other) const +{ + return d == other.d || (d && other.d && *d == *other.d); +} + +/*! + \fn bool QNetworkProxyQuery::operator!=(const QNetworkProxyQuery &other) const + + Returns true if this QNetworkProxyQuery object does not contain + the same data as \a other. +*/ + +/*! + Returns the query type. +*/ +QNetworkProxyQuery::QueryType QNetworkProxyQuery::queryType() const +{ + return d ? d->type : TcpSocket; +} + +/*! + Sets the query type of this object to be \a type. +*/ +void QNetworkProxyQuery::setQueryType(QueryType type) +{ + d->type = type; +} + +/*! + Returns the port number for the outgoing request or -1 if the port + number is not known. + + If the query type is QNetworkProxyQuery::UrlRequest, this function + returns the port number of the URL being requested. In general, + frameworks will fill in the port number from their default values. + + \sa peerHostName(), localPort(), setPeerPort() +*/ +int QNetworkProxyQuery::peerPort() const +{ + return d ? d->remote.port() : -1; +} + +/*! + Sets the requested port number for the outgoing connection to be + \a port. Valid values are 1 to 65535, or -1 to indicate that the + remote port number is unknown. + + The peer port number can also be used to indicate the expected + port number of an incoming connection in the case of + QNetworkProxyQuery::UdpSocket or QNetworkProxyQuery::TcpServer + query types. + + \sa peerPort(), setPeerHostName(), setLocalPort() +*/ +void QNetworkProxyQuery::setPeerPort(int port) +{ + d->remote.setPort(port); +} + +/*! + Returns the host name or IP address being of the outgoing + connection being requested, or an empty string if the remote + hostname is not known. + + If the query type is QNetworkProxyQuery::UrlRequest, this function + returns the host component of the URL being requested. + + \sa peerPort(), localPort(), setPeerHostName() +*/ +QString QNetworkProxyQuery::peerHostName() const +{ + return d ? d->remote.host() : QString(); +} + +/*! + Sets the hostname of the outgoing connection being requested to \a + hostname. An empty hostname can be used to indicate that the + remote host is unknown. + + The peer host name can also be used to indicate the expected + source address of an incoming connection in the case of + QNetworkProxyQuery::UdpSocket or QNetworkProxyQuery::TcpServer + query types. + + \sa peerHostName(), setPeerPort(), setLocalPort() +*/ +void QNetworkProxyQuery::setPeerHostName(const QString &hostname) +{ + d->remote.setHost(hostname); +} + +/*! + Returns the port number of the socket that will accept incoming + packets from remote servers or -1 if the port is not known. + + \sa peerPort(), peerHostName(), setLocalPort() +*/ +int QNetworkProxyQuery::localPort() const +{ + return d ? d->localPort : -1; +} + +/*! + Sets the port number that the socket wishes to use locally to + accept incoming packets from remote servers to \a port. The local + port is most often used with the QNetworkProxyQuery::TcpServer + and QNetworkProxyQuery::UdpSocket query types. + + Valid values are 0 to 65535 (with 0 indicating that any port + number will be acceptable) or -1, which means the local port + number is unknown or not applicable. + + In some circumstances, for special protocols, it's the local port + number can also be used with a query of type + QNetworkProxyQuery::TcpSocket. When that happens, the socket is + indicating it wishes to use the port number \a port when + connecting to a remote host. + + \sa localPort(), setPeerPort(), setPeerHostName() +*/ +void QNetworkProxyQuery::setLocalPort(int port) +{ + d->localPort = port; +} + +/*! + Returns the protocol tag for this QNetworkProxyQuery object, or an + empty QString in case the protocol tag is unknown. + + In the case of queries of type QNetworkProxyQuery::UrlRequest, + this function returns the value of the scheme component of the + URL. + + \sa setProtocolTag(), url() +*/ +QString QNetworkProxyQuery::protocolTag() const +{ + return d ? d->remote.scheme() : QString(); +} + +/*! + Sets the protocol tag for this QNetworkProxyQuery object to be \a + protocolTag. + + The protocol tag is an arbitrary string that indicates which + protocol is being talked over the socket, such as "http", "xmpp", + "telnet", etc. The protocol tag is used by the backend to + return a request that is more specific to the protocol in + question: for example, a HTTP connection could be use a caching + HTTP proxy server, while all other connections use a more powerful + SOCKSv5 proxy server. + + \sa protocolTag() +*/ +void QNetworkProxyQuery::setProtocolTag(const QString &protocolTag) +{ + d->remote.setScheme(protocolTag); +} + +/*! + Returns the URL component of this QNetworkProxyQuery object in + case of a query of type QNetworkProxyQuery::UrlRequest. + + \sa setUrl() +*/ +QUrl QNetworkProxyQuery::url() const +{ + return d ? d->remote : QUrl(); +} + +/*! + Sets the URL component of this QNetworkProxyQuery object to be \a + url. Setting the URL will also set the protocol tag, the remote + host name and port number. This is done so as to facilitate the + implementation of the code that determines the proxy server to be + used. + + \sa url(), peerHostName(), peerPort() +*/ +void QNetworkProxyQuery::setUrl(const QUrl &url) +{ + d->remote = url; +} + +/*! + \class QNetworkProxyFactory + \brief The QNetworkProxyFactory class provides fine-grained proxy selection. + \since 4.5 + + \ingroup network + \inmodule QtNetwork + + QNetworkProxyFactory is an extension to QNetworkProxy, allowing + applications to have a more fine-grained control over which proxy + servers are used, depending on the socket requesting the + proxy. This allows an application to apply different settings, + according to the protocol or destination hostname, for instance. + + QNetworkProxyFactory can be set globally for an application, in + which case it will override any global proxies set with + QNetworkProxy::setApplicationProxy(). If set globally, any sockets + created with Qt will query the factory to determine the proxy to + be used. + + A factory can also be set in certain frameworks that support + multiple connections, such as QNetworkAccessManager. When set on + such object, the factory will be queried for sockets created by + that framework only. + + \section1 System Proxies + + You can configure a factory to use the system proxy's settings. + Call the setUseSystemConfiguration() function with true to enable + this behavior, or false to disable it. + + Similarly, you can use a factory to make queries directly to the + system proxy by calling its systemProxyForQuery() function. + + \warning Depending on the configuration of the user's system, the + use of system proxy features on certain platforms may be subject + to limitations. The systemProxyForQuery() documentation contains a + list of these limitations for those platforms that are affected. +*/ + +/*! + Creates a QNetworkProxyFactory object. + + Since QNetworkProxyFactory is an abstract class, you cannot create + objects of type QNetworkProxyFactory directly. +*/ +QNetworkProxyFactory::QNetworkProxyFactory() +{ +} + +/*! + Destroys the QNetworkProxyFactory object. +*/ +QNetworkProxyFactory::~QNetworkProxyFactory() +{ +} + + +/*! + \since 4.6 + + Enables the use of the platform-specific proxy settings, and only those. + See systemProxyForQuery() for more information. + + Internally, this method (when called with \a enable set to true) + sets an application-wide proxy factory. For this reason, this method + is mutually exclusive with setApplicationProxyFactory(): calling + setApplicationProxyFactory() overrides the use of the system-wide proxy, + and calling setUseSystemConfiguration() overrides any + application proxy or proxy factory that was previously set. + + \note See the systemProxyForQuery() documentation for a list of + limitations related to the use of system proxies. +*/ +void QNetworkProxyFactory::setUseSystemConfiguration(bool enable) +{ + if (enable) { + setApplicationProxyFactory(new QSystemConfigurationProxyFactory); + } else { + setApplicationProxyFactory(0); + } +} + +/*! + Sets the application-wide proxy factory to be \a factory. This + function will take ownership of that object and will delete it + when necessary. + + The application-wide proxy is used as a last-resort when all other + proxy selection requests returned QNetworkProxy::DefaultProxy. For + example, QTcpSocket objects can have a proxy set with + QTcpSocket::setProxy, but if none is set, the proxy factory class + set with this function will be queried. + + If you set a proxy factory with this function, any application + level proxies set with QNetworkProxy::setApplicationProxy will be + overridden. + + \sa QNetworkProxy::setApplicationProxy(), + QAbstractSocket::proxy(), QAbstractSocket::setProxy() +*/ +void QNetworkProxyFactory::setApplicationProxyFactory(QNetworkProxyFactory *factory) +{ + if (globalNetworkProxy()) + globalNetworkProxy()->setApplicationProxyFactory(factory); +} + +/*! + \fn QList<QNetworkProxy> QNetworkProxyFactory::queryProxy(const QNetworkProxyQuery &query) + + This function examines takes the query request, \a query, + examines the details of the type of socket or request and returns + a list of QNetworkProxy objects that indicate the proxy servers to + be used, in order of preference. + + When reimplementing this class, take care to return at least one + element. + + If you cannot determine a better proxy alternative, use + QNetworkProxy::DefaultProxy, which tells the code querying for a + proxy to use a higher alternative. For example, if this factory is + set to a QNetworkAccessManager object, DefaultProxy will tell it + to query the application-level proxy settings. + + If this factory is set as the application proxy factory, + DefaultProxy and NoProxy will have the same meaning. +*/ + +/*! + \fn QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) + + This function examines takes the query request, \a query, + examines the details of the type of socket or request and returns + a list of QNetworkProxy objects that indicate the proxy servers to + be used, in order of preference. + + This function can be used to determine the platform-specific proxy + settings. This function will use the libraries provided by the + operating system to determine the proxy for a given connection, if + such libraries exist. If they don't, this function will just return a + QNetworkProxy of type QNetworkProxy::NoProxy. + + On Windows, this function will use the WinHTTP DLL functions. Despite + its name, Microsoft suggests using it for all applications that + require network connections, not just HTTP. This will respect the + proxy settings set on the registry with the proxycfg.exe tool. If + those settings are not found, this function will attempt to obtain + Internet Explorer's settings and use them. + + On MacOS X, this function will obtain the proxy settings using the + SystemConfiguration framework from Apple. It will apply the FTP, + HTTP and HTTPS proxy configurations for queries that contain the + protocol tag "ftp", "http" and "https", respectively. If the SOCKS + proxy is enabled in that configuration, this function will use the + SOCKS server for all queries. If SOCKS isn't enabled, it will use + the HTTPS proxy for all TcpSocket and UrlRequest queries. + + On other systems, there is no standardised method of obtaining the + system proxy configuration. This function may be improved in + future versions to support those systems. + + \section1 Limitations + + These are the limitations for the current version of this + function. Future versions of Qt may lift some of the limitations + listed here. + + \list + \o On MacOS X, this function will ignore the Proxy Auto Configuration + settings, since it cannot execute the associated ECMAScript code. + + \o On Windows platforms, this function may take several seconds to + execute depending on the configuration of the user's system. + \endlist +*/ + +/*! + This function examines takes the query request, \a query, + examines the details of the type of socket or request and returns + a list of QNetworkProxy objects that indicate the proxy servers to + be used, in order of preference. +*/ +QList<QNetworkProxy> QNetworkProxyFactory::proxyForQuery(const QNetworkProxyQuery &query) +{ + if (!globalNetworkProxy()) + return QList<QNetworkProxy>() << QNetworkProxy(QNetworkProxy::NoProxy); + return globalNetworkProxy()->proxyForQuery(query); +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKPROXY diff --git a/src/network/kernel/qnetworkproxy.h b/src/network/kernel/qnetworkproxy.h new file mode 100644 index 0000000000..26562d533f --- /dev/null +++ b/src/network/kernel/qnetworkproxy.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** 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 QNETWORKPROXY_H +#define QNETWORKPROXY_H + +#include <QtNetwork/qhostaddress.h> +#include <QtCore/qshareddata.h> + +#ifndef QT_NO_NETWORKPROXY + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QUrl; + +class QNetworkProxyQueryPrivate; +class Q_NETWORK_EXPORT QNetworkProxyQuery +{ +public: + enum QueryType { + TcpSocket, + UdpSocket, + TcpServer = 100, + UrlRequest + }; + + QNetworkProxyQuery(); + QNetworkProxyQuery(const QUrl &requestUrl, QueryType queryType = UrlRequest); + QNetworkProxyQuery(const QString &hostname, int port, const QString &protocolTag = QString(), + QueryType queryType = TcpSocket); + QNetworkProxyQuery(quint16 bindPort, const QString &protocolTag = QString(), + QueryType queryType = TcpServer); + QNetworkProxyQuery(const QNetworkProxyQuery &other); + ~QNetworkProxyQuery(); + QNetworkProxyQuery &operator=(const QNetworkProxyQuery &other); + bool operator==(const QNetworkProxyQuery &other) const; + inline bool operator!=(const QNetworkProxyQuery &other) const + { return !(*this == other); } + + QueryType queryType() const; + void setQueryType(QueryType type); + + int peerPort() const; + void setPeerPort(int port); + + QString peerHostName() const; + void setPeerHostName(const QString &hostname); + + int localPort() const; + void setLocalPort(int port); + + QString protocolTag() const; + void setProtocolTag(const QString &protocolTag); + + QUrl url() const; + void setUrl(const QUrl &url); + +private: + QSharedDataPointer<QNetworkProxyQueryPrivate> d; +}; +Q_DECLARE_TYPEINFO(QNetworkProxyQuery, Q_MOVABLE_TYPE); + +class QNetworkProxyPrivate; + +class Q_NETWORK_EXPORT QNetworkProxy +{ +public: + enum ProxyType { + DefaultProxy, + Socks5Proxy, + NoProxy, + HttpProxy, + HttpCachingProxy, + FtpCachingProxy + }; + + enum Capability { + TunnelingCapability = 0x0001, + ListeningCapability = 0x0002, + UdpTunnelingCapability = 0x0004, + CachingCapability = 0x0008, + HostNameLookupCapability = 0x0010 + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + QNetworkProxy(); + QNetworkProxy(ProxyType type, const QString &hostName = QString(), quint16 port = 0, + const QString &user = QString(), const QString &password = QString()); + QNetworkProxy(const QNetworkProxy &other); + QNetworkProxy &operator=(const QNetworkProxy &other); + ~QNetworkProxy(); + bool operator==(const QNetworkProxy &other) const; + inline bool operator!=(const QNetworkProxy &other) const + { return !(*this == other); } + + void setType(QNetworkProxy::ProxyType type); + QNetworkProxy::ProxyType type() const; + + void setCapabilities(Capabilities capab); + Capabilities capabilities() const; + bool isCachingProxy() const; + bool isTransparentProxy() const; + + void setUser(const QString &userName); + QString user() const; + + void setPassword(const QString &password); + QString password() const; + + void setHostName(const QString &hostName); + QString hostName() const; + + void setPort(quint16 port); + quint16 port() const; + + static void setApplicationProxy(const QNetworkProxy &proxy); + static QNetworkProxy applicationProxy(); + +private: + QSharedDataPointer<QNetworkProxyPrivate> d; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QNetworkProxy::Capabilities) + +class Q_NETWORK_EXPORT QNetworkProxyFactory +{ +public: + QNetworkProxyFactory(); + virtual ~QNetworkProxyFactory(); + + virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query = QNetworkProxyQuery()) = 0; + + static void setUseSystemConfiguration(bool enable); + static void setApplicationProxyFactory(QNetworkProxyFactory *factory); + static QList<QNetworkProxy> proxyForQuery(const QNetworkProxyQuery &query); + static QList<QNetworkProxy> systemProxyForQuery(const QNetworkProxyQuery &query = QNetworkProxyQuery()); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_NETWORKPROXY + +#endif // QHOSTINFO_H diff --git a/src/network/kernel/qnetworkproxy_generic.cpp b/src/network/kernel/qnetworkproxy_generic.cpp new file mode 100644 index 0000000000..1591d855c6 --- /dev/null +++ b/src/network/kernel/qnetworkproxy_generic.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 "qnetworkproxy.h" + +#ifndef QT_NO_NETWORKPROXY + +/* + * No system proxy. Just return a list with NoProxy. + */ + +QT_BEGIN_NAMESPACE + +QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &) +{ + return QList<QNetworkProxy>() << QNetworkProxy::NoProxy; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_mac.cpp new file mode 100644 index 0000000000..6fe35ae9f0 --- /dev/null +++ b/src/network/kernel/qnetworkproxy_mac.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** 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 "qnetworkproxy.h" + +#ifndef QT_NO_NETWORKPROXY + +#include <CoreFoundation/CoreFoundation.h> +#include <SystemConfiguration/SystemConfiguration.h> + +#include <QtCore/QRegExp> +#include <QtCore/QStringList> +#include <QtCore/qendian.h> +#include <QtCore/qstringlist.h> +#include "private/qcore_mac_p.h" + +/* + * MacOS X has a proxy configuration module in System Preferences (on + * MacOS X 10.5, it's in Network, Advanced), where one can set the + * proxy settings for: + * + * \list + * \o FTP proxy + * \o Web Proxy (HTTP) + * \o Secure Web Proxy (HTTPS) + * \o Streaming Proxy (RTSP) + * \o SOCKS Proxy + * \o Gopher Proxy + * \o URL for Automatic Proxy Configuration (PAC scripts) + * \o Bypass list (by default: *.local, 169.254/16) + * \endlist + * + * The matching configuration can be obtained by calling SCDynamicStoreCopyProxies + * (from <SystemConfiguration/SCDynamicStoreCopySpecific.h>). See + * Apple's documentation: + * + * http://developer.apple.com/DOCUMENTATION/Networking/Reference/SysConfig/SCDynamicStoreCopySpecific/CompositePage.html#//apple_ref/c/func/SCDynamicStoreCopyProxies + * + */ + +QT_BEGIN_NAMESPACE + +static bool isHostExcluded(CFDictionaryRef dict, const QString &host) +{ + if (host.isEmpty()) + return true; + + bool isSimple = !host.contains(QLatin1Char('.')) && !host.contains(QLatin1Char(':')); + CFNumberRef excludeSimples; + if (isSimple && + (excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExcludeSimpleHostnames))) { + int enabled; + if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled) + return true; + } + + QHostAddress ipAddress; + bool isIpAddress = ipAddress.setAddress(host); + + // not a simple host name + // does it match the list of exclusions? + CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExceptionsList); + if (!exclusionList) + return false; + + CFIndex size = CFArrayGetCount(exclusionList); + for (CFIndex i = 0; i < size; ++i) { + CFStringRef cfentry = (CFStringRef)CFArrayGetValueAtIndex(exclusionList, i); + QString entry = QCFString::toQString(cfentry); + + if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) { + return true; // excluded + } else { + // do wildcard matching + QRegExp rx(entry, Qt::CaseInsensitive, QRegExp::Wildcard); + if (rx.exactMatch(host)) + return true; + } + } + + // host was not excluded + return false; +} + +static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type, + CFStringRef enableKey, CFStringRef hostKey, + CFStringRef portKey) +{ + CFNumberRef protoEnabled; + CFNumberRef protoPort; + CFStringRef protoHost; + if (enableKey + && (protoEnabled = (CFNumberRef)CFDictionaryGetValue(dict, enableKey)) + && (protoHost = (CFStringRef)CFDictionaryGetValue(dict, hostKey)) + && (protoPort = (CFNumberRef)CFDictionaryGetValue(dict, portKey))) { + int enabled; + if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) { + QString host = QCFString::toQString(protoHost); + + int port; + CFNumberGetValue(protoPort, kCFNumberIntType, &port); + + return QNetworkProxy(type, host, port); + } + } + + // proxy not enabled + return QNetworkProxy(); +} + +QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query) +{ + QList<QNetworkProxy> result; + + // obtain a dictionary to the proxy settings: + CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); + if (!dict) { + qWarning("QNetworkProxyFactory::systemProxyForQuery: SCDynamicStoreCopyProxies returned NULL"); + return result; // failed + } + + if (isHostExcluded(dict, query.peerHostName())) { + CFRelease(dict); + return result; // no proxy for this host + } + + // is there a PAC enabled? If so, use it first. + CFNumberRef pacEnabled; + if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigEnable))) { + int enabled; + if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) { + // PAC is enabled + CFStringRef pacUrl = + (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString); + QString url = QCFString::toQString(pacUrl); + + // ### TODO: Use PAC somehow + } + } + + // no PAC, decide which proxy we're looking for based on the query + bool isHttps = false; + QString protocol = query.protocolTag().toLower(); + + // try the protocol-specific proxy + QNetworkProxy protocolSpecificProxy; + if (protocol == QLatin1String("ftp")) { + protocolSpecificProxy = + proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy, + kSCPropNetProxiesFTPEnable, + kSCPropNetProxiesFTPProxy, + kSCPropNetProxiesFTPPort); + } else if (protocol == QLatin1String("http")) { + protocolSpecificProxy = + proxyFromDictionary(dict, QNetworkProxy::HttpProxy, + kSCPropNetProxiesHTTPEnable, + kSCPropNetProxiesHTTPProxy, + kSCPropNetProxiesHTTPPort); + } else if (protocol == QLatin1String("https")) { + isHttps = true; + protocolSpecificProxy = + proxyFromDictionary(dict, QNetworkProxy::HttpProxy, + kSCPropNetProxiesHTTPSEnable, + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + } + if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy) + result << protocolSpecificProxy; + + // let's add SOCKSv5 if present too + QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy, + kSCPropNetProxiesSOCKSEnable, + kSCPropNetProxiesSOCKSProxy, + kSCPropNetProxiesSOCKSPort); + if (socks5.type() != QNetworkProxy::DefaultProxy) + result << socks5; + + // let's add the HTTPS proxy if present (and if we haven't added + // yet) + if (!isHttps) { + QNetworkProxy https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy, + kSCPropNetProxiesHTTPSEnable, + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy) + result << https; + } + + CFRelease(dict); + return result; +} + +QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) +{ + QList<QNetworkProxy> result = macQueryInternal(query); + if (result.isEmpty()) + result << QNetworkProxy::NoProxy; + + return result; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/network/kernel/qnetworkproxy_p.h b/src/network/kernel/qnetworkproxy_p.h new file mode 100644 index 0000000000..21d2cb0ed5 --- /dev/null +++ b/src/network/kernel/qnetworkproxy_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2009 David Faure <dfaure@kdab.net> +** 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 QNETWORKPROXY_P_H +#define QNETWORKPROXY_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_NETWORKPROXY + +QT_BEGIN_NAMESPACE + +class QSystemConfigurationProxyFactory : public QNetworkProxyFactory +{ +public: + QSystemConfigurationProxyFactory() : QNetworkProxyFactory() {} + + virtual QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery& query) + { + QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(query); + + // Make sure NoProxy is in the list, so that QTcpServer can work: + // it searches for the first proxy that can has the ListeningCapability capability + // if none have (as is the case with HTTP proxies), it fails to bind. + // NoProxy allows it to fallback to the 'no proxy' case and bind. + proxies.append(QNetworkProxy::NoProxy); + + return proxies; + } +}; + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE + +#endif + diff --git a/src/network/kernel/qnetworkproxy_symbian.cpp b/src/network/kernel/qnetworkproxy_symbian.cpp new file mode 100644 index 0000000000..79dfb27396 --- /dev/null +++ b/src/network/kernel/qnetworkproxy_symbian.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** 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 FOO 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$ +** +****************************************************************************/ + +/** + * Some notes about the code: + * + * ** It is assumed that the system proxies are for url based requests + * ie. HTTP/HTTPS based. + * ** It is assumed that proxies don't use authentication. + * ** It is assumed that there is no exceptions to proxy use (Symbian side + * does have the field for it but it is not user modifiable by default). + * ** There is no checking for protocol name. + */ + +#include <QtNetwork/qnetworkproxy.h> + +#ifndef QT_NO_NETWORKPROXY + +#include <metadatabase.h> // CMDBSession +#include <commsdattypeinfov1_1.h> // CCDIAPRecord, CCDProxiesRecord +#include <commsdattypesv1_1.h> // KCDTIdIAPRecord, KCDTIdProxiesRecord +#include <QtNetwork/QNetworkConfigurationManager> +#include <QFlags> + +using namespace CommsDat; + +QT_BEGIN_NAMESPACE + +class SymbianIapId +{ +public: + enum State{ + NotValid, + Valid + }; + Q_DECLARE_FLAGS(States, State) + SymbianIapId() {} + ~SymbianIapId() {} + void setIapId(TUint32 iapId) { iapState |= Valid; id = iapId; } + bool isValid() { return iapState == Valid; } + TUint32 iapId() { return id; } +private: + QFlags<States> iapState; + TUint32 id; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(SymbianIapId::States) + +class SymbianProxyQuery +{ +public: + static QNetworkConfiguration findCurrentConfiguration(QNetworkConfigurationManager& configurationManager); + static SymbianIapId getIapId(QNetworkConfigurationManager& configurationManager); + static CCDIAPRecord *getIapRecordLC(TUint32 aIAPId, CMDBSession &aDb); + static CMDBRecordSet<CCDProxiesRecord> *prepareQueryLC(TUint32 serviceId, TDesC& serviceType); + static QList<QNetworkProxy> proxyQueryL(TUint32 aIAPId, const QNetworkProxyQuery &query); +}; + +QNetworkConfiguration SymbianProxyQuery::findCurrentConfiguration(QNetworkConfigurationManager& configurationManager) +{ + QList<QNetworkConfiguration> activeConfigurations = configurationManager.allConfigurations( + QNetworkConfiguration::Active); + QNetworkConfiguration currentConfig; + if (activeConfigurations.count() > 0) { + currentConfig = activeConfigurations.at(0); + } else { + // No active configurations, try default one + QNetworkConfiguration defaultConfiguration = configurationManager.defaultConfiguration(); + if (defaultConfiguration.isValid()) { + switch (defaultConfiguration.type()) { + case QNetworkConfiguration::InternetAccessPoint: + currentConfig = defaultConfiguration; + break; + case QNetworkConfiguration::ServiceNetwork: + { + // Note: This code assumes that the only unambigious way to + // find current proxy config is if there is only one access point + // or if the found access point is immediately usable. + QList<QNetworkConfiguration> childConfigurations = defaultConfiguration.children(); + if (childConfigurations.count() == 1) { + currentConfig = childConfigurations.at(0); + } else { + for (int index = 0; index < childConfigurations.count(); index++) { + QNetworkConfiguration childConfig = childConfigurations.at(index); + if (childConfig.isValid() && childConfig.state() == QNetworkConfiguration::Discovered) { + currentConfig = childConfig; + break; + } + } + } + } + break; + case QNetworkConfiguration::UserChoice: + // User choice is not a valid configuration for proxy discovery + break; + } + } + } + return currentConfig; +} + +SymbianIapId SymbianProxyQuery::getIapId(QNetworkConfigurationManager& configurationManager) +{ + SymbianIapId iapId; + + QNetworkConfiguration currentConfig = findCurrentConfiguration(configurationManager); + if (currentConfig.isValid()) { + // Note: the following code assumes that the identifier is in format + // I_xxxx where xxxx is the identifier of IAP. This is meant as a + // temporary solution until there is a support for returning + // implementation specific identifier. + const int generalPartLength = 2; + const int identifierNumberLength = currentConfig.identifier().length() - generalPartLength; + QString idString(currentConfig.identifier().right(identifierNumberLength)); + bool success; + uint id = idString.toUInt(&success); + if (success) + iapId.setIapId(id); + else + qWarning() << "Failed to convert identifier to access point identifier: " + << currentConfig.identifier(); + } + + return iapId; +} + +CCDIAPRecord *SymbianProxyQuery::getIapRecordLC(TUint32 aIAPId, CMDBSession &aDb) +{ + CCDIAPRecord *iap = static_cast<CCDIAPRecord*> (CCDRecordBase::RecordFactoryL(KCDTIdIAPRecord)); + CleanupStack::PushL(iap); + iap->SetRecordId(aIAPId); + iap->LoadL(aDb); + return iap; +} + +CMDBRecordSet<CCDProxiesRecord> *SymbianProxyQuery::prepareQueryLC(TUint32 serviceId, TDesC& serviceType) +{ + // Create a recordset of type CCDProxiesRecord + // for priming search. + // This will ultimately contain record(s) + // matching the priming record attributes + CMDBRecordSet<CCDProxiesRecord> *proxyRecords = new (ELeave) CMDBRecordSet<CCDProxiesRecord> ( + KCDTIdProxiesRecord); + CleanupStack::PushL(proxyRecords); + + CCDProxiesRecord *primingProxyRecord = + static_cast<CCDProxiesRecord *> (CCDRecordBase::RecordFactoryL(KCDTIdProxiesRecord)); + CleanupStack::PushL(primingProxyRecord); + + primingProxyRecord->iServiceType.SetMaxLengthL(serviceType.Length()); + primingProxyRecord->iServiceType = serviceType; + primingProxyRecord->iService = serviceId; + primingProxyRecord->iUseProxyServer = ETrue; + + proxyRecords->iRecords.AppendL(primingProxyRecord); + // Ownership of primingProxyRecord is transferred to + // proxyRecords, just remove it from the CleanupStack + CleanupStack::Pop(primingProxyRecord); + return proxyRecords; +} + +QList<QNetworkProxy> SymbianProxyQuery::proxyQueryL(TUint32 aIAPId, const QNetworkProxyQuery &query) +{ + QList<QNetworkProxy> foundProxies; + if (query.queryType() != QNetworkProxyQuery::UrlRequest) { + return foundProxies; + } + + CMDBSession *iDb = CMDBSession::NewLC(KCDVersion1_1); + CCDIAPRecord *iap = getIapRecordLC(aIAPId, *iDb); + + // Read service table id and service type + // from the IAP record found + TUint32 serviceId = iap->iService; + RBuf serviceType; + serviceType.CreateL(iap->iServiceType); + CleanupStack::PopAndDestroy(iap); + CleanupClosePushL(serviceType); + + CMDBRecordSet<CCDProxiesRecord> *proxyRecords = prepareQueryLC(serviceId, serviceType); + + // Now to find a proxy table matching our criteria + if (proxyRecords->FindL(*iDb)) { + TInt count = proxyRecords->iRecords.Count(); + for(TInt index = 0; index < count; index++) { + CCDProxiesRecord *proxyRecord = static_cast<CCDProxiesRecord *> (proxyRecords->iRecords[index]); + RBuf serverName; + serverName.CreateL(proxyRecord->iServerName); + CleanupClosePushL(serverName); + if (serverName.Length() == 0) + User::Leave(KErrNotFound); + QString serverNameQt((const QChar*)serverName.Ptr(), serverName.Length()); + CleanupStack::Pop(); // serverName + TUint32 port = proxyRecord->iPortNumber; + + QNetworkProxy proxy(QNetworkProxy::HttpProxy, serverNameQt, port); + foundProxies.append(proxy); + } + } + + CleanupStack::PopAndDestroy(proxyRecords); + CleanupStack::Pop(); // serviceType + CleanupStack::PopAndDestroy(iDb); + + return foundProxies; +} + +QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) +{ + QList<QNetworkProxy> proxies; + SymbianIapId iapId; + TInt error; + QNetworkConfigurationManager manager; + iapId = SymbianProxyQuery::getIapId(manager); + if (iapId.isValid()) { + TRAP(error, proxies = SymbianProxyQuery::proxyQueryL(iapId.iapId(), query)) + if (error != KErrNone) { + qWarning() << "Error while retrieving proxies: '" << error << '"'; + proxies.clear(); + } + } + proxies << QNetworkProxy::NoProxy; + + return proxies; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qnetworkproxy_win.cpp b/src/network/kernel/qnetworkproxy_win.cpp new file mode 100644 index 0000000000..3e374037db --- /dev/null +++ b/src/network/kernel/qnetworkproxy_win.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** 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 "qnetworkproxy.h" + +#ifndef QT_NO_NETWORKPROXY + +#include <qmutex.h> +#include <qstringlist.h> +#include <qregexp.h> +#include <qurl.h> + +#include <string.h> +#include <qt_windows.h> +#include <wininet.h> +#include <private/qsystemlibrary_p.h> + +/* + * Information on the WinHTTP DLL: + * http://msdn.microsoft.com/en-us/library/aa384122(VS.85).aspx example for WPAD + * + * http://msdn.microsoft.com/en-us/library/aa384097(VS.85).aspx WinHttpGetProxyForUrl + * http://msdn.microsoft.com/en-us/library/aa384096(VS.85).aspx WinHttpGetIEProxyConfigForCurrentUs + * http://msdn.microsoft.com/en-us/library/aa384095(VS.85).aspx WinHttpGetDefaultProxyConfiguration + */ + +// We don't want to include winhttp.h because that's not +// present in some Windows SDKs (I don't know why) +// So, instead, copy the definitions here + +typedef struct { + DWORD dwFlags; + DWORD dwAutoDetectFlags; + LPCWSTR lpszAutoConfigUrl; + LPVOID lpvReserved; + DWORD dwReserved; + BOOL fAutoLogonIfChallenged; +} WINHTTP_AUTOPROXY_OPTIONS; + +typedef struct { + DWORD dwAccessType; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} WINHTTP_PROXY_INFO; + +typedef struct { + BOOL fAutoDetect; + LPWSTR lpszAutoConfigUrl; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + +#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 +#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 + +#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 +#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 + +#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 +#define WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 + +#define WINHTTP_NO_PROXY_NAME NULL +#define WINHTTP_NO_PROXY_BYPASS NULL + +#define WINHTTP_ERROR_BASE 12000 +#define ERROR_WINHTTP_LOGIN_FAILURE (WINHTTP_ERROR_BASE + 15) +#define ERROR_WINHTTP_AUTODETECTION_FAILED (WINHTTP_ERROR_BASE + 180) + +QT_BEGIN_NAMESPACE + +typedef BOOL (WINAPI * PtrWinHttpGetProxyForUrl)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS*, WINHTTP_PROXY_INFO*); +typedef HINTERNET (WINAPI * PtrWinHttpOpen)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR,DWORD); +typedef BOOL (WINAPI * PtrWinHttpGetDefaultProxyConfiguration)(WINHTTP_PROXY_INFO*); +typedef BOOL (WINAPI * PtrWinHttpGetIEProxyConfigForCurrentUser)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG*); +typedef BOOL (WINAPI * PtrWinHttpCloseHandle)(HINTERNET); +static PtrWinHttpGetProxyForUrl ptrWinHttpGetProxyForUrl = 0; +static PtrWinHttpOpen ptrWinHttpOpen = 0; +static PtrWinHttpGetDefaultProxyConfiguration ptrWinHttpGetDefaultProxyConfiguration = 0; +static PtrWinHttpGetIEProxyConfigForCurrentUser ptrWinHttpGetIEProxyConfigForCurrentUser = 0; +static PtrWinHttpCloseHandle ptrWinHttpCloseHandle = 0; + + +static QStringList splitSpaceSemicolon(const QString &source) +{ + QStringList list; + int start = 0; + int end; + while (true) { + int space = source.indexOf(QLatin1Char(' '), start); + int semicolon = source.indexOf(QLatin1Char(';'), start); + end = space; + if (semicolon != -1 && (end == -1 || semicolon < end)) + end = semicolon; + + if (end == -1) { + if (start != source.length()) + list.append(source.mid(start)); + return list; + } + if (start != end) + list.append(source.mid(start, end - start)); + start = end + 1; + } + return list; +} + +static bool isBypassed(const QString &host, const QStringList &bypassList) +{ + if (host.isEmpty()) + return true; + + bool isSimple = !host.contains(QLatin1Char('.')) && !host.contains(QLatin1Char(':')); + + QHostAddress ipAddress; + bool isIpAddress = ipAddress.setAddress(host); + + // does it match the list of exclusions? + foreach (const QString &entry, bypassList) { + if (isSimple && entry == QLatin1String("<local>")) + return true; + if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) { + return true; // excluded + } else { + // do wildcard matching + QRegExp rx(entry, Qt::CaseInsensitive, QRegExp::Wildcard); + if (rx.exactMatch(host)) + return true; + } + } + + // host was not excluded + return false; +} + +static QList<QNetworkProxy> parseServerList(const QNetworkProxyQuery &query, const QStringList &proxyList) +{ + // Reference documentation from Microsoft: + // http://msdn.microsoft.com/en-us/library/aa383912(VS.85).aspx + // + // According to the website, the proxy server list is + // one or more of the space- or semicolon-separated strings in the format: + // ([<scheme>=][<scheme>"://"]<server>[":"<port>]) + + QList<QNetworkProxy> result; + foreach (const QString &entry, proxyList) { + int server = 0; + + int pos = entry.indexOf(QLatin1Char('=')); + if (pos != -1) { + QStringRef scheme = entry.leftRef(pos); + if (scheme != query.protocolTag()) + continue; + + server = pos + 1; + } + + QNetworkProxy::ProxyType proxyType = QNetworkProxy::HttpProxy; + quint16 port = 8080; + + pos = entry.indexOf(QLatin1String("://"), server); + if (pos != -1) { + QStringRef scheme = entry.midRef(server, pos - server); + if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) { + // no-op + // defaults are above + } else if (scheme == QLatin1String("socks") || scheme == QLatin1String("socks5")) { + proxyType = QNetworkProxy::Socks5Proxy; + port = 1080; + } else { + // unknown proxy type + continue; + } + + server = pos + 3; + } + + pos = entry.indexOf(QLatin1Char(':'), server); + if (pos != -1) { + bool ok; + uint value = entry.mid(pos + 1).toUInt(&ok); + if (!ok || value > 65535) + continue; // invalid port number + + port = value; + } else { + pos = entry.length(); + } + + result << QNetworkProxy(proxyType, entry.mid(server, pos - server), port); + } + + return result; +} + +class QWindowsSystemProxy +{ +public: + QWindowsSystemProxy(); + ~QWindowsSystemProxy(); + void init(); + + QMutex mutex; + + HINTERNET hHttpSession; + WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions; + + QString autoConfigUrl; + QStringList proxyServerList; + QStringList proxyBypass; + QList<QNetworkProxy> defaultResult; + + bool initialized; + bool functional; + bool isAutoConfig; +}; + +Q_GLOBAL_STATIC(QWindowsSystemProxy, systemProxy) + +QWindowsSystemProxy::QWindowsSystemProxy() + : initialized(false), functional(false), isAutoConfig(false) +{ + defaultResult << QNetworkProxy::NoProxy; +} + +QWindowsSystemProxy::~QWindowsSystemProxy() +{ + if (hHttpSession) + ptrWinHttpCloseHandle(hHttpSession); +} + +void QWindowsSystemProxy::init() +{ + if (initialized) + return; + initialized = true; + +#ifdef Q_OS_WINCE + // Windows CE does not have any of the following API + return; +#else + // load the winhttp.dll library + QSystemLibrary lib(L"winhttp"); + if (!lib.load()) + return; // failed to load + + ptrWinHttpOpen = (PtrWinHttpOpen)lib.resolve("WinHttpOpen"); + ptrWinHttpCloseHandle = (PtrWinHttpCloseHandle)lib.resolve("WinHttpCloseHandle"); + ptrWinHttpGetProxyForUrl = (PtrWinHttpGetProxyForUrl)lib.resolve("WinHttpGetProxyForUrl"); + ptrWinHttpGetDefaultProxyConfiguration = (PtrWinHttpGetDefaultProxyConfiguration)lib.resolve("WinHttpGetDefaultProxyConfiguration"); + ptrWinHttpGetIEProxyConfigForCurrentUser = (PtrWinHttpGetIEProxyConfigForCurrentUser)lib.resolve("WinHttpGetIEProxyConfigForCurrentUser"); + + // Try to obtain the Internet Explorer configuration. + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig; + if (ptrWinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig)) { + if (ieProxyConfig.lpszAutoConfigUrl) { + autoConfigUrl = QString::fromWCharArray(ieProxyConfig.lpszAutoConfigUrl); + GlobalFree(ieProxyConfig.lpszAutoConfigUrl); + } + if (ieProxyConfig.lpszProxy) { + // http://msdn.microsoft.com/en-us/library/aa384250%28VS.85%29.aspx speaks only about a "proxy URL", + // not multiple URLs. However we tested this and it can return multiple URLs. So we use splitSpaceSemicolon + // on it. + proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxy)); + GlobalFree(ieProxyConfig.lpszProxy); + } + if (ieProxyConfig.lpszProxyBypass) { + proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxyBypass)); + GlobalFree(ieProxyConfig.lpszProxyBypass); + } + } + + hHttpSession = NULL; + if (ieProxyConfig.fAutoDetect || !autoConfigUrl.isEmpty()) { + // using proxy autoconfiguration + proxyServerList.clear(); + proxyBypass.clear(); + + // open the handle and obtain the options + hHttpSession = ptrWinHttpOpen(L"Qt System Proxy access/1.0", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!hHttpSession) + return; + + isAutoConfig = true; + memset(&autoProxyOptions, 0, sizeof autoProxyOptions); + autoProxyOptions.fAutoLogonIfChallenged = false; + if (ieProxyConfig.fAutoDetect) { + autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; + autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | + WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } else { + autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + autoProxyOptions.lpszAutoConfigUrl = (LPCWSTR)autoConfigUrl.utf16(); + } + } else { + // not auto-detected + // attempt to get the static configuration instead + WINHTTP_PROXY_INFO proxyInfo; + if (ptrWinHttpGetDefaultProxyConfiguration(&proxyInfo) && + proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) { + // we got information from the registry + // overwrite the IE configuration, if any + + proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxyBypass)); + proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy)); + } + + if (proxyInfo.lpszProxy) + GlobalFree(proxyInfo.lpszProxy); + if (proxyInfo.lpszProxyBypass) + GlobalFree(proxyInfo.lpszProxyBypass); + } + + functional = isAutoConfig || !proxyServerList.isEmpty(); +#endif +} + +QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query) +{ + QWindowsSystemProxy *sp = systemProxy(); + if (!sp) + return QList<QNetworkProxy>() << QNetworkProxy(); + + QMutexLocker locker(&sp->mutex); + sp->init(); + if (!sp->functional) + return sp->defaultResult; + + if (sp->isAutoConfig) { + WINHTTP_PROXY_INFO proxyInfo; + + // try to get the proxy config for the URL + QUrl url = query.url(); + // url could be empty, e.g. from QNetworkProxy::applicationProxy(), that's fine, + // we'll still ask for the proxy. + // But for a file url, we know we don't need one. + if (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc")) + return sp->defaultResult; + if (query.queryType() != QNetworkProxyQuery::UrlRequest) { + // change the scheme to https, maybe it'll work + url.setScheme(QLatin1String("https")); + } + + bool getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession, + (LPCWSTR)url.toString().utf16(), + &sp->autoProxyOptions, + &proxyInfo); + DWORD getProxyError = GetLastError(); + + if (!getProxySucceeded + && (ERROR_WINHTTP_LOGIN_FAILURE == getProxyError)) { + // We first tried without AutoLogon, because this might prevent caching the result. + // But now we've to enable it (http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx) + sp->autoProxyOptions.fAutoLogonIfChallenged = TRUE; + getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession, + (LPCWSTR)url.toString().utf16(), + &sp->autoProxyOptions, + &proxyInfo); + getProxyError = GetLastError(); + } + + if (getProxySucceeded) { + // yes, we got a config for this URL + QString proxyBypass = QString::fromWCharArray(proxyInfo.lpszProxyBypass); + QStringList proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy)); + if (proxyInfo.lpszProxy) + GlobalFree(proxyInfo.lpszProxy); + if (proxyInfo.lpszProxyBypass) + GlobalFree(proxyInfo.lpszProxyBypass); + + if (isBypassed(query.peerHostName(), splitSpaceSemicolon(proxyBypass))) + return sp->defaultResult; + return parseServerList(query, proxyServerList); + } + + // GetProxyForUrl failed + + if (ERROR_WINHTTP_AUTODETECTION_FAILED == getProxyError) { + //No config file could be retrieved on the network. + //Don't search for it next time again. + sp->isAutoConfig = false; + } + + return sp->defaultResult; + } + + // static configuration + if (isBypassed(query.peerHostName(), sp->proxyBypass)) + return sp->defaultResult; + + QList<QNetworkProxy> result = parseServerList(query, sp->proxyServerList); + // In some cases, this was empty. See SF task 00062670 + if (result.isEmpty()) + return sp->defaultResult; + + return result; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/kernel/qurlinfo.cpp b/src/network/kernel/qurlinfo.cpp new file mode 100644 index 0000000000..cff4912904 --- /dev/null +++ b/src/network/kernel/qurlinfo.cpp @@ -0,0 +1,731 @@ +/**************************************************************************** +** +** 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 "qurlinfo.h" + +#ifndef QT_NO_URLINFO + +#include "qurl.h" +#include "qdir.h" +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QUrlInfoPrivate +{ +public: + QUrlInfoPrivate() : + permissions(0), + size(0), + isDir(false), + isFile(true), + isSymLink(false), + isWritable(true), + isReadable(true), + isExecutable(false) + {} + + QString name; + int permissions; + QString owner; + QString group; + qint64 size; + + QDateTime lastModified; + QDateTime lastRead; + bool isDir; + bool isFile; + bool isSymLink; + bool isWritable; + bool isReadable; + bool isExecutable; +}; + + +/*! + \class QUrlInfo + \brief The QUrlInfo class stores information about URLs. + + \ingroup io + \ingroup network + + The information about a URL that can be retrieved includes name(), + permissions(), owner(), group(), size(), lastModified(), + lastRead(), isDir(), isFile(), isSymLink(), isWritable(), + isReadable() and isExecutable(). + + You can create your own QUrlInfo objects passing in all the + relevant information in the constructor, and you can modify a + QUrlInfo; for each getter mentioned above there is an equivalent + setter. Note that setting values does not affect the underlying + resource that the QUrlInfo provides information about; for example + if you call setWritable(true) on a read-only resource the only + thing changed is the QUrlInfo object, not the resource. + + \sa QUrl, {FTP Example} +*/ + +/*! + \enum QUrlInfo::PermissionSpec + + This enum is used by the permissions() function to report the + permissions of a file. + + \value ReadOwner The file is readable by the owner of the file. + \value WriteOwner The file is writable by the owner of the file. + \value ExeOwner The file is executable by the owner of the file. + \value ReadGroup The file is readable by the group. + \value WriteGroup The file is writable by the group. + \value ExeGroup The file is executable by the group. + \value ReadOther The file is readable by anyone. + \value WriteOther The file is writable by anyone. + \value ExeOther The file is executable by anyone. +*/ + +/*! + Constructs an invalid QUrlInfo object with default values. + + \sa isValid() +*/ + +QUrlInfo::QUrlInfo() +{ + d = 0; +} + +/*! + Copy constructor, copies \a ui to this URL info object. +*/ + +QUrlInfo::QUrlInfo(const QUrlInfo &ui) +{ + if (ui.d) { + d = new QUrlInfoPrivate; + *d = *ui.d; + } else { + d = 0; + } +} + +/*! + Constructs a QUrlInfo object by specifying all the URL's + information. + + The information that is passed is the \a name, file \a + permissions, \a owner and \a group and the file's \a size. Also + passed is the \a lastModified date/time and the \a lastRead + date/time. Flags are also passed, specifically, \a isDir, \a + isFile, \a isSymLink, \a isWritable, \a isReadable and \a + isExecutable. +*/ + +QUrlInfo::QUrlInfo(const QString &name, int permissions, const QString &owner, + const QString &group, qint64 size, const QDateTime &lastModified, + const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink, + bool isWritable, bool isReadable, bool isExecutable) +{ + d = new QUrlInfoPrivate; + d->name = name; + d->permissions = permissions; + d->owner = owner; + d->group = group; + d->size = size; + d->lastModified = lastModified; + d->lastRead = lastRead; + d->isDir = isDir; + d->isFile = isFile; + d->isSymLink = isSymLink; + d->isWritable = isWritable; + d->isReadable = isReadable; + d->isExecutable = isExecutable; +} + + +/*! + Constructs a QUrlInfo object by specifying all the URL's + information. + + The information that is passed is the \a url, file \a + permissions, \a owner and \a group and the file's \a size. Also + passed is the \a lastModified date/time and the \a lastRead + date/time. Flags are also passed, specifically, \a isDir, \a + isFile, \a isSymLink, \a isWritable, \a isReadable and \a + isExecutable. +*/ + +QUrlInfo::QUrlInfo(const QUrl &url, int permissions, const QString &owner, + const QString &group, qint64 size, const QDateTime &lastModified, + const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink, + bool isWritable, bool isReadable, bool isExecutable) +{ + d = new QUrlInfoPrivate; + d->name = QFileInfo(url.path()).fileName(); + d->permissions = permissions; + d->owner = owner; + d->group = group; + d->size = size; + d->lastModified = lastModified; + d->lastRead = lastRead; + d->isDir = isDir; + d->isFile = isFile; + d->isSymLink = isSymLink; + d->isWritable = isWritable; + d->isReadable = isReadable; + d->isExecutable = isExecutable; +} + + +/*! + Sets the name of the URL to \a name. The name is the full text, + for example, "http://qt.nokia.com/doc/qurlinfo.html". + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setName(const QString &name) +{ + if (!d) + d = new QUrlInfoPrivate; + d->name = name; +} + + +/*! + If \a b is true then the URL is set to be a directory; if \a b is + false then the URL is set not to be a directory (which normally + means it is a file). (Note that a URL can refer to both a file and + a directory even though most file systems do not support this.) + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setDir(bool b) +{ + if (!d) + d = new QUrlInfoPrivate; + d->isDir = b; +} + + +/*! + If \a b is true then the URL is set to be a file; if \b is false + then the URL is set not to be a file (which normally means it is a + directory). (Note that a URL can refer to both a file and a + directory even though most file systems do not support this.) + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setFile(bool b) +{ + if (!d) + d = new QUrlInfoPrivate; + d->isFile = b; +} + + +/*! + Specifies that the URL refers to a symbolic link if \a b is true + and that it does not if \a b is false. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setSymLink(bool b) +{ + if (!d) + d = new QUrlInfoPrivate; + d->isSymLink = b; +} + + +/*! + Specifies that the URL is writable if \a b is true and not + writable if \a b is false. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setWritable(bool b) +{ + if (!d) + d = new QUrlInfoPrivate; + d->isWritable = b; +} + + +/*! + Specifies that the URL is readable if \a b is true and not + readable if \a b is false. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setReadable(bool b) +{ + if (!d) + d = new QUrlInfoPrivate; + d->isReadable = b; +} + +/*! + Specifies that the owner of the URL is called \a s. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setOwner(const QString &s) +{ + if (!d) + d = new QUrlInfoPrivate; + d->owner = s; +} + +/*! + Specifies that the owning group of the URL is called \a s. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setGroup(const QString &s) +{ + if (!d) + d = new QUrlInfoPrivate; + d->group = s; +} + +/*! + Specifies the \a size of the URL. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setSize(qint64 size) +{ + if (!d) + d = new QUrlInfoPrivate; + d->size = size; +} + +/*! + Specifies that the URL has access permissions \a p. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setPermissions(int p) +{ + if (!d) + d = new QUrlInfoPrivate; + d->permissions = p; +} + +/*! + Specifies that the object the URL refers to was last modified at + \a dt. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setLastModified(const QDateTime &dt) +{ + if (!d) + d = new QUrlInfoPrivate; + d->lastModified = dt; +} + +/*! + \since 4.4 + + Specifies that the object the URL refers to was last read at + \a dt. + + If you call this function for an invalid URL info, this function + turns it into a valid one. + + \sa isValid() +*/ + +void QUrlInfo::setLastRead(const QDateTime &dt) +{ + if (!d) + d = new QUrlInfoPrivate; + d->lastRead = dt; +} + +/*! + Destroys the URL info object. +*/ + +QUrlInfo::~QUrlInfo() +{ + delete d; +} + +/*! + Assigns the values of \a ui to this QUrlInfo object. +*/ + +QUrlInfo &QUrlInfo::operator=(const QUrlInfo &ui) +{ + if (ui.d) { + if (!d) + d= new QUrlInfoPrivate; + *d = *ui.d; + } else { + delete d; + d = 0; + } + return *this; +} + +/*! + Returns the file name of the URL. + + \sa isValid() +*/ + +QString QUrlInfo::name() const +{ + if (!d) + return QString(); + return d->name; +} + +/*! + Returns the permissions of the URL. You can use the \c PermissionSpec flags + to test for certain permissions. + + \sa isValid() +*/ + +int QUrlInfo::permissions() const +{ + if (!d) + return 0; + return d->permissions; +} + +/*! + Returns the owner of the URL. + + \sa isValid() +*/ + +QString QUrlInfo::owner() const +{ + if (!d) + return QString(); + return d->owner; +} + +/*! + Returns the group of the URL. + + \sa isValid() +*/ + +QString QUrlInfo::group() const +{ + if (!d) + return QString(); + return d->group; +} + +/*! + Returns the size of the URL. + + \sa isValid() +*/ + +qint64 QUrlInfo::size() const +{ + if (!d) + return 0; + return d->size; +} + +/*! + Returns the last modification date of the URL. + + \sa isValid() +*/ + +QDateTime QUrlInfo::lastModified() const +{ + if (!d) + return QDateTime(); + return d->lastModified; +} + +/*! + Returns the date when the URL was last read. + + \sa isValid() +*/ + +QDateTime QUrlInfo::lastRead() const +{ + if (!d) + return QDateTime(); + return d->lastRead; +} + +/*! + Returns true if the URL is a directory; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isDir() const +{ + if (!d) + return false; + return d->isDir; +} + +/*! + Returns true if the URL is a file; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isFile() const +{ + if (!d) + return false; + return d->isFile; +} + +/*! + Returns true if the URL is a symbolic link; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isSymLink() const +{ + if (!d) + return false; + return d->isSymLink; +} + +/*! + Returns true if the URL is writable; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isWritable() const +{ + if (!d) + return false; + return d->isWritable; +} + +/*! + Returns true if the URL is readable; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isReadable() const +{ + if (!d) + return false; + return d->isReadable; +} + +/*! + Returns true if the URL is executable; otherwise returns false. + + \sa isValid() +*/ + +bool QUrlInfo::isExecutable() const +{ + if (!d) + return false; + return d->isExecutable; +} + +/*! + Returns true if \a i1 is greater than \a i2; otherwise returns + false. The objects are compared by the value, which is specified + by \a sortBy. This must be one of QDir::Name, QDir::Time or + QDir::Size. +*/ + +bool QUrlInfo::greaterThan(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy) +{ + switch (sortBy) { + case QDir::Name: + return i1.name() > i2.name(); + case QDir::Time: + return i1.lastModified() > i2.lastModified(); + case QDir::Size: + return i1.size() > i2.size(); + default: + return false; + } +} + +/*! + Returns true if \a i1 is less than \a i2; otherwise returns false. + The objects are compared by the value, which is specified by \a + sortBy. This must be one of QDir::Name, QDir::Time or QDir::Size. +*/ + +bool QUrlInfo::lessThan(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy) +{ + return !greaterThan(i1, i2, sortBy); +} + +/*! + Returns true if \a i1 equals to \a i2; otherwise returns false. + The objects are compared by the value, which is specified by \a + sortBy. This must be one of QDir::Name, QDir::Time or QDir::Size. +*/ + +bool QUrlInfo::equal(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy) +{ + switch (sortBy) { + case QDir::Name: + return i1.name() == i2.name(); + case QDir::Time: + return i1.lastModified() == i2.lastModified(); + case QDir::Size: + return i1.size() == i2.size(); + default: + return false; + } +} + +/*! + Returns true if this QUrlInfo is equal to \a other; otherwise + returns false. + + \sa lessThan(), equal() +*/ + +bool QUrlInfo::operator==(const QUrlInfo &other) const +{ + if (!d) + return other.d == 0; + if (!other.d) + return false; + + return (d->name == other.d->name && + d->permissions == other.d->permissions && + d->owner == other.d->owner && + d->group == other.d->group && + d->size == other.d->size && + d->lastModified == other.d->lastModified && + d->lastRead == other.d->lastRead && + d->isDir == other.d->isDir && + d->isFile == other.d->isFile && + d->isSymLink == other.d->isSymLink && + d->isWritable == other.d->isWritable && + d->isReadable == other.d->isReadable && + d->isExecutable == other.d->isExecutable); +} + +/*! + \fn bool QUrlInfo::operator!=(const QUrlInfo &other) const + \since 4.2 + + Returns true if this QUrlInfo is not equal to \a other; otherwise + returns false. + + \sa lessThan(), equal() +*/ + +/*! + Returns true if the URL info is valid; otherwise returns false. + Valid means that the QUrlInfo contains real information. + + You should always check if the URL info is valid before relying on + the values. +*/ +bool QUrlInfo::isValid() const +{ + return d != 0; +} + +QT_END_NAMESPACE + +#endif // QT_NO_URLINFO diff --git a/src/network/kernel/qurlinfo.h b/src/network/kernel/qurlinfo.h new file mode 100644 index 0000000000..d40bf0c44d --- /dev/null +++ b/src/network/kernel/qurlinfo.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 QURLINFO_H +#define QURLINFO_H + +#include <QtCore/qdatetime.h> +#include <QtCore/qstring.h> +#include <QtCore/qiodevice.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_URLINFO + +class QUrl; +class QUrlInfoPrivate; + +class Q_NETWORK_EXPORT QUrlInfo +{ +public: + enum PermissionSpec { + ReadOwner = 00400, WriteOwner = 00200, ExeOwner = 00100, + ReadGroup = 00040, WriteGroup = 00020, ExeGroup = 00010, + ReadOther = 00004, WriteOther = 00002, ExeOther = 00001 }; + + QUrlInfo(); + QUrlInfo(const QUrlInfo &ui); + QUrlInfo(const QString &name, int permissions, const QString &owner, + const QString &group, qint64 size, const QDateTime &lastModified, + const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink, + bool isWritable, bool isReadable, bool isExecutable); + QUrlInfo(const QUrl &url, int permissions, const QString &owner, + const QString &group, qint64 size, const QDateTime &lastModified, + const QDateTime &lastRead, bool isDir, bool isFile, bool isSymLink, + bool isWritable, bool isReadable, bool isExecutable); + QUrlInfo &operator=(const QUrlInfo &ui); + virtual ~QUrlInfo(); + + virtual void setName(const QString &name); + virtual void setDir(bool b); + virtual void setFile(bool b); + virtual void setSymLink(bool b); + virtual void setOwner(const QString &s); + virtual void setGroup(const QString &s); + virtual void setSize(qint64 size); + virtual void setWritable(bool b); + virtual void setReadable(bool b); + virtual void setPermissions(int p); + virtual void setLastModified(const QDateTime &dt); + void setLastRead(const QDateTime &dt); + + bool isValid() const; + + QString name() const; + int permissions() const; + QString owner() const; + QString group() const; + qint64 size() const; + QDateTime lastModified() const; + QDateTime lastRead() const; + bool isDir() const; + bool isFile() const; + bool isSymLink() const; + bool isWritable() const; + bool isReadable() const; + bool isExecutable() const; + + static bool greaterThan(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy); + static bool lessThan(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy); + static bool equal(const QUrlInfo &i1, const QUrlInfo &i2, + int sortBy); + + bool operator==(const QUrlInfo &i) const; + inline bool operator!=(const QUrlInfo &i) const + { return !operator==(i); } + +private: + QUrlInfoPrivate *d; +}; + +#endif // QT_NO_URLINFO + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QURLINFO_H diff --git a/src/network/network.pro b/src/network/network.pro new file mode 100644 index 0000000000..948922b8c1 --- /dev/null +++ b/src/network/network.pro @@ -0,0 +1,31 @@ +# Qt network module + +TARGET = QtNetwork +QPRO_PWD = $$PWD +DEFINES += QT_BUILD_NETWORK_LIB QT_NO_USING_NAMESPACE +#DEFINES += QLOCALSERVER_DEBUG QLOCALSOCKET_DEBUG +#DEFINES += QNETWORKDISKCACHE_DEBUG +#DEFINES += QSSLSOCKET_DEBUG +#DEFINES += QHOSTINFO_DEBUG +#DEFINES += QABSTRACTSOCKET_DEBUG QNATIVESOCKETENGINE_DEBUG +#DEFINES += QTCPSOCKETENGINE_DEBUG QTCPSOCKET_DEBUG QTCPSERVER_DEBUG QSSLSOCKET_DEBUG +#DEFINES += QUDPSOCKET_DEBUG QUDPSERVER_DEBUG +QT = core +win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x64000000 + +unix|win32-g++*:QMAKE_PKGCONFIG_REQUIRES = QtCore + +include(../qbase.pri) +include(access/access.pri) +include(bearer/bearer.pri) +include(kernel/kernel.pri) +include(socket/socket.pri) +include(ssl/ssl.pri) + +QMAKE_LIBS += $$QMAKE_LIBS_NETWORK + + +symbian { + TARGET.UID3=0x2001B2DE + LIBS += -lesock -linsock -lcertstore -lefsrv -lctframework +} diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp new file mode 100644 index 0000000000..7af71ccc8b --- /dev/null +++ b/src/network/socket/qabstractsocket.cpp @@ -0,0 +1,2920 @@ +/**************************************************************************** +** +** 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 QABSTRACTSOCKET_DEBUG + +/*! + \class QAbstractSocket + + \brief The QAbstractSocket class provides the base functionality + common to all socket types. + + \reentrant + \ingroup network + \inmodule QtNetwork + + QAbstractSocket is the base class for QTcpSocket and QUdpSocket + and contains all common functionality of these two classes. If + you need a socket, you have two options: + + \list + \i Instantiate QTcpSocket or QUdpSocket. + \i Create a native socket descriptor, instantiate + QAbstractSocket, and call setSocketDescriptor() to wrap the + native socket. + \endlist + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. UDP + (User Datagram Protocol) is an unreliable, datagram-oriented, + connectionless protocol. In practice, this means that TCP is + better suited for continuous transmission of data, whereas the + more lightweight UDP can be used when reliability isn't + important. + + QAbstractSocket's API unifies most of the differences between the + two protocols. For example, although UDP is connectionless, + connectToHost() establishes a virtual connection for UDP sockets, + enabling you to use QAbstractSocket in more or less the same way + regardless of the underlying protocol. Internally, + QAbstractSocket remembers the address and port passed to + connectToHost(), and functions like read() and write() use these + values. + + At any time, QAbstractSocket has a state (returned by + state()). The initial state is UnconnectedState. After + calling connectToHost(), the socket first enters + HostLookupState. If the host is found, QAbstractSocket enters + ConnectingState and emits the hostFound() signal. When the + connection has been established, it enters ConnectedState and + emits connected(). If an error occurs at any stage, error() is + emitted. Whenever the state changes, stateChanged() is emitted. + For convenience, isValid() returns true if the socket is ready for + reading and writing, but note that the socket's state must be + ConnectedState before reading and writing can occur. + + Read or write data by calling read() or write(), or use the + convenience functions readLine() and readAll(). QAbstractSocket + also inherits getChar(), putChar(), and ungetChar() from + QIODevice, which work on single bytes. The bytesWritten() signal + is emitted when data has been written to the socket (i.e., when + the client has read the data). Note that Qt does not limit the + write buffer size. You can monitor its size by listening to this + signal. + + The readyRead() signal is emitted every time a new chunk of data + has arrived. bytesAvailable() then returns the number of bytes + that are available for reading. Typically, you would connect the + readyRead() signal to a slot and read all available data there. + If you don't read all the data at once, the remaining data will + still be available later, and any new incoming data will be + appended to QAbstractSocket's internal read buffer. To limit the + size of the read buffer, call setReadBufferSize(). + + To close the socket, call disconnectFromHost(). QAbstractSocket enters + QAbstractSocket::ClosingState. After all pending data has been written to + the socket, QAbstractSocket actually closes the socket, enters + QAbstractSocket::ClosedState, and emits disconnected(). If you want to + abort a connection immediately, discarding all pending data, call abort() + instead. If the remote host closes the connection, QAbstractSocket will + emit error(QAbstractSocket::RemoteHostClosedError), during which the socket + state will still be ConnectedState, and then the disconnected() signal + will be emitted. + + The port and address of the connected peer is fetched by calling + peerPort() and peerAddress(). peerName() returns the host name of + the peer, as passed to connectToHost(). localPort() and + localAddress() return the port and address of the local socket. + + QAbstractSocket provides a set of functions that suspend the + calling thread until certain signals are emitted. These functions + can be used to implement blocking sockets: + + \list + \o waitForConnected() blocks until a connection has been established. + + \o waitForReadyRead() blocks until new data is available for + reading. + + \o waitForBytesWritten() blocks until one payload of data has been + written to the socket. + + \o waitForDisconnected() blocks until the connection has closed. + \endlist + + We show an example: + + \snippet doc/src/snippets/network/tcpwait.cpp 0 + + If \l{QIODevice::}{waitForReadyRead()} returns false, the + connection has been closed or an error has occurred. + + Programming with a blocking socket is radically different from + programming with a non-blocking socket. A blocking socket doesn't + require an event loop and typically leads to simpler code. + However, in a GUI application, blocking sockets should only be + used in non-GUI threads, to avoid freezing the user interface. + See the \l network/fortuneclient and \l network/blockingfortuneclient + examples for an overview of both approaches. + + \note We discourage the use of the blocking functions together + with signals. One of the two possibilities should be used. + + QAbstractSocket can be used with QTextStream and QDataStream's + stream operators (operator<<() and operator>>()). There is one + issue to be aware of, though: You must make sure that enough data + is available before attempting to read it using operator>>(). + + \sa QFtp, QNetworkAccessManager, QTcpServer +*/ + +/*! + \fn void QAbstractSocket::hostFound() + + This signal is emitted after connectToHost() has been called and + the host lookup has succeeded. + + \note Since Qt 4.6.3 QAbstractSocket may emit hostFound() + directly from the connectToHost() call since a DNS result could have been + cached. + + \sa connected() +*/ + +/*! + \fn void QAbstractSocket::connected() + + This signal is emitted after connectToHost() has been called and + a connection has been successfully established. + + \note On some operating systems the connected() signal may + be directly emitted from the connectToHost() call for connections + to the localhost. + + \sa connectToHost(), disconnected() +*/ + +/*! + \fn void QAbstractSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \warning If you need to delete the sender() of this signal in a slot connected + to it, use the \l{QObject::deleteLater()}{deleteLater()} function. + + \sa connectToHost(), disconnectFromHost(), abort() +*/ + +/*! + \fn void QAbstractSocket::error(QAbstractSocket::SocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QAbstractSocket::SocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa error(), errorString(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QAbstractSocket::stateChanged(QAbstractSocket::SocketState socketState) + + This signal is emitted whenever QAbstractSocket's state changes. + The \a socketState parameter is the new state. + + QAbstractSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_REGISTER_METATYPE() and + qRegisterMetaType(). + + \sa state(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QAbstractSocket::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 +*/ + +/*! + \enum QAbstractSocket::NetworkLayerProtocol + + This enum describes the network layer protocol values used in Qt. + + \value IPv4Protocol IPv4 + \value IPv6Protocol IPv6 + \value UnknownNetworkLayerProtocol Other than IPv4 and IPv6 + + \sa QHostAddress::protocol() +*/ + +/*! + \enum QAbstractSocket::SocketType + + This enum describes the transport layer protocol. + + \value TcpSocket TCP + \value UdpSocket UDP + \value UnknownSocketType Other than TCP and UDP + + \sa QAbstractSocket::socketType() +*/ + +/*! + \enum QAbstractSocket::SocketError + + This enum describes the socket errors that can occur. + + \value ConnectionRefusedError The connection was refused by the + peer (or timed out). + \value RemoteHostClosedError The remote host closed the + connection. Note that the client socket (i.e., this socket) + will be closed after the remote close notification has + been sent. + \value HostNotFoundError The host address was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the + operating system's limit (which can be as low as 8192 + bytes). + \value NetworkError An error occurred with the network (e.g., the + network cable was accidentally plugged out). + \value AddressInUseError The address specified to QUdpSocket::bind() is + already in use and was set to be exclusive. + \value SocketAddressNotAvailableError The address specified to + QUdpSocket::bind() does not belong to the host. + \value UnsupportedSocketOperationError The requested socket operation is + not supported by the local operating system (e.g., lack of + IPv6 support). + \value ProxyAuthenticationRequiredError The socket is using a proxy, and + the proxy requires authentication. + \value SslHandshakeFailedError The SSL/TLS handshake failed, so + the connection was closed (only used in QSslSocket) + \value UnfinishedSocketOperationError Used by QAbstractSocketEngine only, + The last operation attempted has not finished yet (still in progress in + the background). + \value ProxyConnectionRefusedError Could not contact the proxy server because + the connection to that server was denied + \value ProxyConnectionClosedError The connection to the proxy server was closed + unexpectedly (before the connection to the final peer was established) + \value ProxyConnectionTimeoutError The connection to the proxy server timed out + or the proxy server stopped responding in the authentication phase. + \value ProxyNotFoundError The proxy address set with setProxy() (or the application + proxy) was not found. + \value ProxyProtocolError The connection negotiation with the proxy server + because the response from the proxy server could not be understood. + + \value UnknownSocketError An unidentified error occurred. + \sa QAbstractSocket::error() +*/ + +/*! + \enum QAbstractSocket::SocketState + + This enum describes the different states in which a socket can be. + + \value UnconnectedState The socket is not connected. + \value HostLookupState The socket is performing a host name lookup. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value BoundState The socket is bound to an address and port (for servers). + \value ClosingState The socket is about to close (data may still + be waiting to be written). + \value ListeningState For internal use only. + \omitvalue Idle + \omitvalue HostLookup + \omitvalue Connecting + \omitvalue Connected + \omitvalue Closing + \omitvalue Connection + + \sa QAbstractSocket::state() +*/ + +/*! + \enum QAbstractSocket::SocketOption + \since 4.6 + + This enum represents the options that can be set on a socket. + If desired, they can be set after having received the connected() signal from + the socket or after having received a new socket from a QTcpServer. + + \value LowDelayOption Try to optimize the socket for low latency. For a QTcpSocket + this would set the TCP_NODELAY option and disable Nagle's algorithm. Set this to 1 + to enable. + \value KeepAliveOption Set this to 1 to enable the SO_KEEPALIVE socket option + + \value MulticastTtlOption Set this to an integer value to set IP_MULTICAST_TTL (TTL for multicast datagrams) socket option. + + \value MulticastLoopbackOption Set this to 1 to enable the IP_MULTICAST_LOOP (multicast loopback) socket option. + + \sa QAbstractSocket::setSocketOption(), QAbstractSocket::socketOption() +*/ + +#include "qabstractsocket.h" +#include "qabstractsocket_p.h" + +#include "private/qhostinfo_p.h" +#include "private/qnetworksession_p.h" + +#include <qabstracteventdispatcher.h> +#include <qhostaddress.h> +#include <qhostinfo.h> +#include <qmetaobject.h> +#include <qpointer.h> +#include <qtimer.h> +#include <qelapsedtimer.h> +#include <qscopedvaluerollback.h> + +#ifndef QT_NO_OPENSSL +#include <QtNetwork/qsslsocket.h> +#endif + +#include <private/qthread_p.h> + +#ifdef QABSTRACTSOCKET_DEBUG +#include <qdebug.h> +#endif + +#include <time.h> + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +#ifndef QABSTRACTSOCKET_BUFFERSIZE +#define QABSTRACTSOCKET_BUFFERSIZE 32768 +#endif +#define QT_CONNECT_TIMEOUT 30000 +#define QT_TRANSFER_TIMEOUT 120000 + +QT_BEGIN_NAMESPACE + +#if defined QABSTRACTSOCKET_DEBUG +QT_BEGIN_INCLUDE_NAMESPACE +#include <qstring.h> +#include <ctype.h> +QT_END_INCLUDE_NAMESPACE + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} +#endif + +static bool isProxyError(QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ProxyAuthenticationRequiredError: + case QAbstractSocket::ProxyConnectionRefusedError: + case QAbstractSocket::ProxyConnectionClosedError: + case QAbstractSocket::ProxyConnectionTimeoutError: + case QAbstractSocket::ProxyNotFoundError: + case QAbstractSocket::ProxyProtocolError: + return true; + default: + return false; + } +} + +/*! \internal + + Constructs a QAbstractSocketPrivate. Initializes all members. +*/ +QAbstractSocketPrivate::QAbstractSocketPrivate() + : readSocketNotifierCalled(false), + readSocketNotifierState(false), + readSocketNotifierStateSet(false), + emittedReadyRead(false), + emittedBytesWritten(false), + abortCalled(false), + closeCalled(false), + pendingClose(false), + port(0), + localPort(0), + peerPort(0), + socketEngine(0), + cachedSocketDescriptor(-1), + readBufferMaxSize(0), + readBuffer(QABSTRACTSOCKET_BUFFERSIZE), + writeBuffer(QABSTRACTSOCKET_BUFFERSIZE), + isBuffered(false), + blockingTimeout(30000), + connectTimer(0), + disconnectTimer(0), + connectTimeElapsed(0), + hostLookupId(-1), + socketType(QAbstractSocket::UnknownSocketType), + state(QAbstractSocket::UnconnectedState), + socketError(QAbstractSocket::UnknownSocketError) +{ +} + +/*! \internal + + Destructs the QAbstractSocket. If the socket layer is open, it + will be reset. +*/ +QAbstractSocketPrivate::~QAbstractSocketPrivate() +{ +} + +/*! \internal + + Resets the socket layer, clears the read and write buffers and + deletes any socket notifiers. +*/ +void QAbstractSocketPrivate::resetSocketLayer() +{ +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::resetSocketLayer()"); +#endif + + if (socketEngine) { + socketEngine->close(); + socketEngine->disconnect(); + delete socketEngine; + socketEngine = 0; + cachedSocketDescriptor = -1; + } + if (connectTimer) + connectTimer->stop(); + if (disconnectTimer) + disconnectTimer->stop(); +} + +/*! \internal + + Initializes the socket layer to by of type \a type, using the + network layer protocol \a protocol. Resets the socket layer first + if it's already initialized. Sets up the socket notifiers. +*/ +bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol) +{ +#ifdef QT_NO_NETWORKPROXY + // this is here to avoid a duplication of the call to createSocketEngine below + static const QNetworkProxy &proxyInUse = *(QNetworkProxy *)0; +#endif + + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + QString typeStr; + if (q->socketType() == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (q->socketType() == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + else typeStr = QLatin1String("UnknownSocketType"); + QString protocolStr; + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + else protocolStr = QLatin1String("UnknownNetworkLayerProtocol"); +#endif + + resetSocketLayer(); + socketEngine = QAbstractSocketEngine::createSocketEngine(q->socketType(), proxyInUse, q); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + socketEngine->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + if (!socketEngine) { + socketError = QAbstractSocket::UnsupportedSocketOperationError; + q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + return false; + } + if (!socketEngine->initialize(q->socketType(), protocol)) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) failed (%s)", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), + socketEngine->errorString().toLatin1().constData()); +#endif + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + return false; + } + + if (threadData->eventDispatcher) + socketEngine->setReceiver(this); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) success", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData()); +#endif + return true; +} + +/*! \internal + + Slot connected to the read socket notifier. This slot is called + when new data is available for reading, or when the socket has + been closed. Handles recursive calls. +*/ +bool QAbstractSocketPrivate::canReadNotification() +{ + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification()"); +#endif + + // Prevent recursive calls + if (readSocketNotifierCalled) { + if (!readSocketNotifierStateSet) { + readSocketNotifierStateSet = true; + readSocketNotifierState = socketEngine->isReadNotificationEnabled(); + socketEngine->setReadNotificationEnabled(false); + } + } + QScopedValueRollback<bool> rsncrollback(readSocketNotifierCalled); + readSocketNotifierCalled = true; + + if (!isBuffered) + socketEngine->setReadNotificationEnabled(false); + + // If buffered, read data from the socket into the read buffer + qint64 newBytes = 0; + if (isBuffered) { + // Return if there is no space in the buffer + if (readBufferMaxSize && readBuffer.size() >= readBufferMaxSize) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() buffer is full"); +#endif + return false; + } + + // If reading from the socket fails after getting a read + // notification, close the socket. + newBytes = readBuffer.size(); + if (!readFromSocket()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() disconnecting socket"); +#endif + q->disconnectFromHost(); + return false; + } + newBytes = readBuffer.size() - newBytes; + + // If read buffer is full, disable the read socket notifier. + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) { + socketEngine->setReadNotificationEnabled(false); + } + } + + // only emit readyRead() when not recursing, and only if there is data available + bool hasData = newBytes > 0 +#ifndef QT_NO_UDPSOCKET + || (!isBuffered && socketType != QAbstractSocket::TcpSocket && socketEngine && socketEngine->hasPendingDatagrams()) +#endif + || (!isBuffered && socketType == QAbstractSocket::TcpSocket && socketEngine) + ; + + if (!emittedReadyRead && hasData) { + QScopedValueRollback<bool> r(emittedReadyRead); + emittedReadyRead = true; + emit q->readyRead(); + } + + // If we were closed as a result of the readyRead() signal, + // return. + if (state == QAbstractSocket::UnconnectedState || state == QAbstractSocket::ClosingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() socket is closing - returning"); +#endif + return true; + } + + if (!hasData && socketEngine) + socketEngine->setReadNotificationEnabled(true); + + // reset the read socket notifier state if we reentered inside the + // readyRead() connected slot. + if (readSocketNotifierStateSet && socketEngine && + readSocketNotifierState != socketEngine->isReadNotificationEnabled()) { + socketEngine->setReadNotificationEnabled(readSocketNotifierState); + readSocketNotifierStateSet = false; + } + return true; +} + +/*! \internal + + Slot connected to the write socket notifier. It's called during a + delayed connect or when the socket is ready for writing. +*/ +bool QAbstractSocketPrivate::canWriteNotification() +{ +#if defined (Q_OS_WIN) + if (socketEngine && socketEngine->isWriteNotificationEnabled()) + socketEngine->setWriteNotificationEnabled(false); +#endif + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canWriteNotification() flushing"); +#endif + int tmp = writeBuffer.size(); + flush(); + + if (socketEngine) { +#if defined (Q_OS_WIN) + if (!writeBuffer.isEmpty()) + socketEngine->setWriteNotificationEnabled(true); +#else + if (writeBuffer.isEmpty() && socketEngine->bytesToWrite() == 0) + socketEngine->setWriteNotificationEnabled(false); +#endif + } + + return (writeBuffer.size() < tmp); +} + +/*! \internal + + Slot connected to a notification of connection status + change. Either we finished connecting or we failed to connect. +*/ +void QAbstractSocketPrivate::connectionNotification() +{ + // If in connecting state, check if the connection has been + // established, otherwise flush pending data. + if (state == QAbstractSocket::ConnectingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::connectionNotification() testing connection"); +#endif + _q_testConnection(); + } +} + +/*! \internal + + Writes pending data in the write buffers to the socket. The + function writes as much as it can without blocking. + + It is usually invoked by canWriteNotification after one or more + calls to write(). + + Emits bytesWritten(). +*/ +bool QAbstractSocketPrivate::flush() +{ + Q_Q(QAbstractSocket); + if (!socketEngine || !socketEngine->isValid() || (writeBuffer.isEmpty() + && socketEngine->bytesToWrite() == 0)) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() nothing to do: valid ? %s, writeBuffer.isEmpty() ? %s", + socketEngine->isValid() ? "yes" : "no", writeBuffer.isEmpty() ? "yes" : "no"); +#endif + + // this covers the case when the buffer was empty, but we had to wait for the socket engine to finish + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + + return false; + } + + int nextSize = writeBuffer.nextDataBlockSize(); + const char *ptr = writeBuffer.readPointer(); + + // Attempt to write it all in one chunk. + qint64 written = socketEngine->write(ptr, nextSize); + if (written < 0) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug() << "QAbstractSocketPrivate::flush() write error, aborting." << socketEngine->errorString(); +#endif + emit q->error(socketError); + // an unexpected error so close the socket. + q->abort(); + return false; + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() %lld bytes written to the network", + written); +#endif + + // Remove what we wrote so far. + writeBuffer.free(written); + if (written > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + QScopedValueRollback<bool> r(emittedBytesWritten); + emittedBytesWritten = true; + emit q->bytesWritten(written); + } + } + + if (writeBuffer.isEmpty() && socketEngine && socketEngine->isWriteNotificationEnabled() + && !socketEngine->bytesToWrite()) + socketEngine->setWriteNotificationEnabled(false); + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + + return true; +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +void QAbstractSocketPrivate::resolveProxy(const QString &hostname, quint16 port) +{ + QHostAddress parsed; + if (hostname == QLatin1String("localhost") + || hostname.startsWith(QLatin1String("localhost.")) + || (parsed.setAddress(hostname) + && (parsed == QHostAddress::LocalHost + || parsed == QHostAddress::LocalHostIPv6))) { + proxyInUse = QNetworkProxy::NoProxy; + return; + } + + QList<QNetworkProxy> proxies; + + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(hostname, port, QString(), + socketType == QAbstractSocket::TcpSocket ? + QNetworkProxyQuery::TcpSocket : + QNetworkProxyQuery::UdpSocket); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (socketType == QAbstractSocket::UdpSocket && + (p.capabilities() & QNetworkProxy::UdpTunnelingCapability) == 0) + continue; + + if (socketType == QAbstractSocket::TcpSocket && + (p.capabilities() & QNetworkProxy::TunnelingCapability) == 0) + continue; + + proxyInUse = p; + return; + } + + // no proxy found + // DefaultProxy here will raise an error + proxyInUse = QNetworkProxy(); +} + +/*! + \internal + + Starts the connection to \a host, like _q_startConnecting below, + but without hostname resolution. +*/ +void QAbstractSocketPrivate::startConnectingByName(const QString &host) +{ + Q_Q(QAbstractSocket); + if (state == QAbstractSocket::ConnectingState || state == QAbstractSocket::ConnectedState) + return; + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::startConnectingByName(host == %s)", qPrintable(host)); +#endif + + // ### Let the socket engine drive this? + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + connectTimeElapsed = 0; + + if (initSocketLayer(QAbstractSocket::UnknownNetworkLayerProtocol)) { + if (socketEngine->connectToHostByName(host, port) || + socketEngine->state() == QAbstractSocket::ConnectingState) { + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + return; + } + + // failed to connect + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + + state = QAbstractSocket::UnconnectedState; + emit q->error(socketError); + emit q->stateChanged(state); +} + +#endif + +/*! \internal + + Slot connected to QHostInfo::lookupHost() in connectToHost(). This + function starts the process of connecting to any number of + candidate IP addresses for the host, if it was found. Calls + _q_connectToNextAddress(). +*/ +void QAbstractSocketPrivate::_q_startConnecting(const QHostInfo &hostInfo) +{ + Q_Q(QAbstractSocket); + if (state != QAbstractSocket::HostLookupState) + return; + + if (hostLookupId != -1 && hostLookupId != hostInfo.lookupId()) { + qWarning("QAbstractSocketPrivate::_q_startConnecting() received hostInfo for wrong lookup ID %d expected %d", hostInfo.lookupId(), hostLookupId); + } + + addresses = hostInfo.addresses(); + +#if defined(QABSTRACTSOCKET_DEBUG) + QString s = QLatin1String("{"); + for (int i = 0; i < addresses.count(); ++i) { + if (i != 0) s += QLatin1String(", "); + s += addresses.at(i).toString(); + } + s += QLatin1Char('}'); + qDebug("QAbstractSocketPrivate::_q_startConnecting(hostInfo == %s)", s.toLatin1().constData()); +#endif + + // Try all addresses twice. + addresses += addresses; + + // If there are no addresses in the host list, report this to the + // user. + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_startConnecting(), host not found"); +#endif + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::HostNotFoundError; + q->setErrorString(QAbstractSocket::tr("Host not found")); + emit q->stateChanged(state); + emit q->error(QAbstractSocket::HostNotFoundError); + return; + } + + // Enter Connecting state (see also sn_write, which is called by + // the write socket notifier after connect()) + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + // Report the successful host lookup + emit q->hostFound(); + + // Reset the total time spent connecting. + connectTimeElapsed = 0; + + // The addresses returned by the lookup will be tested one after + // another by _q_connectToNextAddress(). + _q_connectToNextAddress(); +} + +/*! \internal + + Called by a queued or direct connection from _q_startConnecting() or + _q_testConnection(), this function takes the first address of the + pending addresses list and tries to connect to it. If the + connection succeeds, QAbstractSocket will emit + connected(). Otherwise, error(ConnectionRefusedError) or + error(SocketTimeoutError) is emitted. +*/ +void QAbstractSocketPrivate::_q_connectToNextAddress() +{ + Q_Q(QAbstractSocket); + do { + // Check for more pending addresses + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), all addresses failed."); +#endif + state = QAbstractSocket::UnconnectedState; + if (socketEngine) { + if ((socketEngine->error() == QAbstractSocket::UnknownSocketError +#ifdef Q_OS_AIX + // On AIX, the second connect call will result in EINVAL and not + // ECONNECTIONREFUSED; although the meaning is the same. + || socketEngine->error() == QAbstractSocket::UnsupportedSocketOperationError +#endif + ) && socketEngine->state() == QAbstractSocket::ConnectingState) { + socketError = QAbstractSocket::ConnectionRefusedError; + q->setErrorString(QAbstractSocket::tr("Connection refused")); + } else { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + } else { +// socketError = QAbstractSocket::ConnectionRefusedError; +// q->setErrorString(QAbstractSocket::tr("Connection refused")); + } + emit q->stateChanged(state); + emit q->error(socketError); + return; + } + + // Pick the first host address candidate + host = addresses.takeFirst(); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connecting to %s:%i, %d left to try", + host.toString().toLatin1().constData(), port, addresses.count()); +#endif + +#if defined(QT_NO_IPV6) + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + // If we have no IPv6 support, then we will not be able to + // connect. So we just pretend we didn't see this address. +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), skipping IPv6 entry"); +#endif + continue; + } +#endif + + if (!initSocketLayer(host.protocol())) { + // hope that the next address is better +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), failed to initialize sock layer"); +#endif + continue; + } + + // Tries to connect to the address. If it succeeds immediately + // (localhost address on BSD or any UDP connect), emit + // connected() and return. + if (socketEngine->connectToHost(host, port)) { + //_q_testConnection(); + fetchConnectionParameters(); + return; + } + + // cache the socket descriptor even if we're not fully connected yet + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + // Check that we're in delayed connection state. If not, try + // the next address + if (socketEngine->state() != QAbstractSocket::ConnectingState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connection failed (%s)", + socketEngine->errorString().toLatin1().constData()); +#endif + continue; + } + + // Start the connect timer. + if (threadData->eventDispatcher) { + if (!connectTimer) { + connectTimer = new QTimer(q); + QObject::connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + } + connectTimer->start(QT_CONNECT_TIMEOUT); + } + + // Wait for a write notification that will eventually call + // _q_testConnection(). + socketEngine->setWriteNotificationEnabled(true); + break; + } while (state != QAbstractSocket::ConnectedState); +} + +/*! \internal + + Tests if a connection has been established. If it has, connected() + is emitted. Otherwise, _q_connectToNextAddress() is invoked. +*/ +void QAbstractSocketPrivate::_q_testConnection() +{ + if (socketEngine) { + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + + if (socketEngine->state() == QAbstractSocket::ConnectedState) { + // Fetch the parameters if our connection is completed; + // otherwise, fall out and try the next address. + fetchConnectionParameters(); + if (pendingClose) { + q_func()->disconnectFromHost(); + pendingClose = false; + } + return; + } + + // don't retry the other addresses if we had a proxy error + if (isProxyError(socketEngine->error())) + addresses.clear(); + } + + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_testConnection() connection failed," + " checking for alternative addresses"); +#endif + _q_connectToNextAddress(); +} + +/*! \internal + + This function is called after a certain number of seconds has + passed while waiting for a connection. It simply tests the + connection, and continues to the next address if the connection + failed. +*/ +void QAbstractSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_abortConnectionAttempt() (timed out)"); +#endif + if (socketEngine) + socketEngine->setWriteNotificationEnabled(false); + + connectTimer->stop(); + + if (addresses.isEmpty()) { + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::SocketTimeoutError; + q->setErrorString(QAbstractSocket::tr("Connection timed out")); + emit q->stateChanged(state); + emit q->error(socketError); + } else { + _q_connectToNextAddress(); + } +} + +void QAbstractSocketPrivate::_q_forceDisconnect() +{ + Q_Q(QAbstractSocket); + if (socketEngine && socketEngine->isValid() && state == QAbstractSocket::ClosingState) { + socketEngine->close(); + q->disconnectFromHost(); + } +} + +/*! \internal + + Reads data from the socket layer into the read buffer. Returns + true on success; otherwise false. +*/ +bool QAbstractSocketPrivate::readFromSocket() +{ + Q_Q(QAbstractSocket); + // Find how many bytes we can read from the socket layer. + qint64 bytesToRead = socketEngine->bytesAvailable(); + if (bytesToRead == 0) { + // Under heavy load, certain conditions can trigger read notifications + // for socket notifiers on which there is no activity. If we continue + // to read 0 bytes from the socket, we will trigger behavior similar + // to that which signals a remote close. When we hit this condition, + // we try to read 4k of data from the socket, which will give us either + // an EAGAIN/EWOULDBLOCK if the connection is alive (i.e., the remote + // host has _not_ disappeared). + bytesToRead = 4096; + } + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) + bytesToRead = readBufferMaxSize - readBuffer.size(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() about to read %d bytes", + int(bytesToRead)); +#endif + + // Read from the socket, store data in the read buffer. + char *ptr = readBuffer.reserve(bytesToRead); + qint64 readBytes = socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + readBuffer.chop(bytesToRead); + return true; + } + readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() got %d bytes, buffer size = %d", + int(readBytes), readBuffer.size()); +#endif + + if (!socketEngine->isValid()) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + emit q->error(socketError); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() read failed: %s", + q->errorString().toLatin1().constData()); +#endif + resetSocketLayer(); + return false; + } + + return true; +} + +/*! \internal + + Sets up the internal state after the connection has succeeded. +*/ +void QAbstractSocketPrivate::fetchConnectionParameters() +{ + Q_Q(QAbstractSocket); + + peerName = hostName; + if (socketEngine) { + socketEngine->setReadNotificationEnabled(true); + socketEngine->setWriteNotificationEnabled(true); + localPort = socketEngine->localPort(); + peerPort = socketEngine->peerPort(); + localAddress = socketEngine->localAddress(); + peerAddress = socketEngine->peerAddress(); + cachedSocketDescriptor = socketEngine->socketDescriptor(); + } + + state = QAbstractSocket::ConnectedState; + emit q->stateChanged(state); + emit q->connected(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::fetchConnectionParameters() connection to %s:%i established", + host.toString().toLatin1().constData(), port); +#endif +} + + +void QAbstractSocketPrivate::pauseSocketNotifiers(QAbstractSocket *socket) +{ + QAbstractSocketEngine *socketEngine = socket->d_func()->socketEngine; + if (!socketEngine) + return; + socket->d_func()->prePauseReadSocketNotifierState = socketEngine->isReadNotificationEnabled(); + socket->d_func()->prePauseWriteSocketNotifierState = socketEngine->isWriteNotificationEnabled(); + socket->d_func()->prePauseExceptionSocketNotifierState = socketEngine->isExceptionNotificationEnabled(); + socketEngine->setReadNotificationEnabled(false); + socketEngine->setWriteNotificationEnabled(false); + socketEngine->setExceptionNotificationEnabled(false); +} + +void QAbstractSocketPrivate::resumeSocketNotifiers(QAbstractSocket *socket) +{ + QAbstractSocketEngine *socketEngine = socket->d_func()->socketEngine; + if (!socketEngine) + return; + socketEngine->setReadNotificationEnabled(socket->d_func()->prePauseReadSocketNotifierState); + socketEngine->setWriteNotificationEnabled(socket->d_func()->prePauseWriteSocketNotifierState); + socketEngine->setExceptionNotificationEnabled(socket->d_func()->prePauseExceptionSocketNotifierState); +} + +QAbstractSocketEngine* QAbstractSocketPrivate::getSocketEngine(QAbstractSocket *socket) +{ + return socket->d_func()->socketEngine; +} + + +/*! \internal + + Constructs a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, + QAbstractSocketPrivate &dd, QObject *parent) + : QIODevice(dd, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%sSocket, QAbstractSocketPrivate == %p, parent == %p)", + socketType == TcpSocket ? "Tcp" : socketType == UdpSocket + ? "Udp" : "Unknown", &dd, parent); +#endif + d->socketType = socketType; +} + +/*! + Creates a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. + + \sa socketType(), QTcpSocket, QUdpSocket +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, QObject *parent) + : QIODevice(*new QAbstractSocketPrivate, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%p)", parent); +#endif + d->socketType = socketType; +} + +/*! + Destroys the socket. +*/ +QAbstractSocket::~QAbstractSocket() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::~QAbstractSocket()"); +#endif + if (d->state != UnconnectedState) + abort(); +} + +/*! + Returns true if the socket is valid and ready for use; otherwise + returns false. + + \bold{Note:} The socket's state must be ConnectedState before reading and + writing can occur. + + \sa state() +*/ +bool QAbstractSocket::isValid() const +{ + return d_func()->socketEngine ? d_func()->socketEngine->isValid() : isOpen(); +} + +/*! + Attempts to make a connection to \a hostName on the given \a port. + + The socket is opened in the given \a openMode and first enters + HostLookupState, then performs a host name lookup of \a hostName. + If the lookup succeeds, hostFound() is emitted and QAbstractSocket + enters ConnectingState. It then attempts to connect to the address + or addresses returned by the lookup. Finally, if a connection is + established, QAbstractSocket enters ConnectedState and + emits connected(). + + At any point, the socket can emit error() to signal that an error + occurred. + + \a hostName may be an IP address in string form (e.g., + "43.195.83.32"), or it may be a host name (e.g., + "example.com"). QAbstractSocket will do a lookup only if + required. \a port is in native byte order. + + \sa state(), peerName(), peerAddress(), peerPort(), waitForConnected() +*/ +void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, + OpenMode openMode) +{ + QMetaObject::invokeMethod(this, "connectToHostImplementation", + Qt::DirectConnection, + Q_ARG(QString, hostName), + Q_ARG(quint16, port), + Q_ARG(OpenMode, openMode)); +} + +/*! + \since 4.1 + + Contains the implementation of connectToHost(). + + Attempts to make a connection to \a hostName on the given \a + port. The socket is opened in the given \a openMode. +*/ +void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint16 port, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i, %i)...", qPrintable(hostName), port, + (int) openMode); +#endif + + if (d->state == ConnectedState || d->state == ConnectingState + || d->state == ClosingState || d->state == HostLookupState) { + qWarning("QAbstractSocket::connectToHost() called when already looking up or connecting/connected to \"%s\"", qPrintable(hostName)); + return; + } + + d->hostName = hostName; + d->port = port; + d->state = UnconnectedState; + d->readBuffer.clear(); + d->writeBuffer.clear(); + d->abortCalled = false; + d->closeCalled = false; + d->pendingClose = false; + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName = hostName; + if (d->hostLookupId != -1) { + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + } + +#ifndef QT_NO_NETWORKPROXY + // Get the proxy information + d->resolveProxy(hostName, port); + if (d->proxyInUse.type() == QNetworkProxy::DefaultProxy) { + // failed to setup the proxy + d->socketError = QAbstractSocket::UnsupportedSocketOperationError; + setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + emit error(d->socketError); + return; + } +#endif + + if (openMode & QIODevice::Unbuffered) + d->isBuffered = false; // Unbuffered QTcpSocket + else if (!d_func()->isBuffered) + openMode |= QAbstractSocket::Unbuffered; // QUdpSocket + + QIODevice::open(openMode); + d->state = HostLookupState; + emit stateChanged(d->state); + + QHostAddress temp; + if (temp.setAddress(hostName)) { + QHostInfo info; + info.setAddresses(QList<QHostAddress>() << temp); + d->_q_startConnecting(info); +#ifndef QT_NO_NETWORKPROXY + } else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) { + // the proxy supports connection by name, so use it + d->startConnectingByName(hostName); + return; +#endif + } else { + if (d->threadData->eventDispatcher) { + // this internal API for QHostInfo either immediately gives us the desired + // QHostInfo from cache or later calls the _q_startConnecting slot. + bool immediateResultValid = false; + QHostInfo hostInfo = qt_qhostinfo_lookup(hostName, + this, + SLOT(_q_startConnecting(QHostInfo)), + &immediateResultValid, + &d->hostLookupId); + if (immediateResultValid) { + d->hostLookupId = -1; + d->_q_startConnecting(hostInfo); + } + } + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i) == %s%s", hostName.toLatin1().constData(), port, + (d->state == ConnectedState) ? "true" : "false", + (d->state == ConnectingState || d->state == HostLookupState) + ? " (connection in progress)" : ""); +#endif +} + +/*! \overload + + Attempts to make a connection to \a address on port \a port. +*/ +void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, + OpenMode openMode) +{ +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost([%s], %i, %i)...", + address.toString().toLatin1().constData(), port, (int) openMode); +#endif + connectToHost(address.toString(), port, openMode); +} + +/*! + Returns the number of bytes that are waiting to be written. The + bytes are written when control goes back to the event loop or + when flush() is called. + + \sa bytesAvailable(), flush() +*/ +qint64 QAbstractSocket::bytesToWrite() const +{ + Q_D(const QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesToWrite() == %i", d->writeBuffer.size()); +#endif + return (qint64)d->writeBuffer.size(); +} + +/*! + Returns the number of incoming bytes that are waiting to be read. + + \sa bytesToWrite(), read() +*/ +qint64 QAbstractSocket::bytesAvailable() const +{ + Q_D(const QAbstractSocket); + qint64 available = QIODevice::bytesAvailable(); + + available += (qint64) d->readBuffer.size(); + + if (!d->isBuffered && d->socketEngine && d->socketEngine->isValid()) + available += d->socketEngine->bytesAvailable(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesAvailable() == %llu", available); +#endif + return available; +} + +/*! + Returns the host port number (in native byte order) of the local + socket if available; otherwise returns 0. + + \sa localAddress(), peerPort(), setLocalPort() +*/ +quint16 QAbstractSocket::localPort() const +{ + Q_D(const QAbstractSocket); + return d->localPort; +} + +/*! + Returns the host address of the local socket if available; + otherwise returns QHostAddress::Null. + + This is normally the main IP address of the host, but can be + QHostAddress::LocalHost (127.0.0.1) for connections to the + local host. + + \sa localPort(), peerAddress(), setLocalAddress() +*/ +QHostAddress QAbstractSocket::localAddress() const +{ + Q_D(const QAbstractSocket); + return d->localAddress; +} + +/*! + Returns the port of the connected peer if the socket is in + ConnectedState; otherwise returns 0. + + \sa peerAddress(), localPort(), setPeerPort() +*/ +quint16 QAbstractSocket::peerPort() const +{ + Q_D(const QAbstractSocket); + return d->peerPort; +} + +/*! + Returns the address of the connected peer if the socket is in + ConnectedState; otherwise returns QHostAddress::Null. + + \sa peerName(), peerPort(), localAddress(), setPeerAddress() +*/ +QHostAddress QAbstractSocket::peerAddress() const +{ + Q_D(const QAbstractSocket); + return d->peerAddress; +} + +/*! + Returns the name of the peer as specified by connectToHost(), or + an empty QString if connectToHost() has not been called. + + \sa peerAddress(), peerPort(), setPeerName() +*/ +QString QAbstractSocket::peerName() const +{ + Q_D(const QAbstractSocket); + return d->peerName.isEmpty() ? d->hostName : d->peerName; +} + +/*! + Returns true if a line of data can be read from the socket; + otherwise returns false. + + \sa readLine() +*/ +bool QAbstractSocket::canReadLine() const +{ + bool hasLine = d_func()->readBuffer.canReadLine(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::canReadLine() == %s, buffer size = %d, size = %d", hasLine ? "true" : "false", + d_func()->readBuffer.size(), d_func()->buffer.size()); +#endif + return hasLine || QIODevice::canReadLine(); +} + +/*! + Returns the native socket descriptor of the QAbstractSocket object + if this is available; otherwise returns -1. + + If the socket is using QNetworkProxy, the returned descriptor + may not be usable with native socket functions. + + The socket descriptor is not available when QAbstractSocket is in + UnconnectedState. + + \sa setSocketDescriptor() +*/ +int QAbstractSocket::socketDescriptor() const +{ + Q_D(const QAbstractSocket); + return d->cachedSocketDescriptor; +} + +/*! + Initializes QAbstractSocket with the native socket descriptor \a + socketDescriptor. Returns true if \a socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. + The socket is opened in the mode specified by \a openMode, and + enters the socket state specified by \a socketState. + + \bold{Note:} It is not possible to initialize two abstract sockets + with the same native socket descriptor. + + \sa socketDescriptor() +*/ +bool QAbstractSocket::setSocketDescriptor(int socketDescriptor, SocketState socketState, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->setSocketDescriptor(socketDescriptor, socketState, openMode); +#endif + + d->resetSocketLayer(); + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine) { + d->socketError = UnsupportedSocketOperationError; + setErrorString(tr("Operation on socket is not supported")); + return false; + } + bool result = d->socketEngine->initialize(socketDescriptor, socketState); + if (!result) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return false; + } + + if (d->threadData->eventDispatcher) + d->socketEngine->setReceiver(d); + + QIODevice::open(openMode); + + if (d->state != socketState) { + d->state = socketState; + emit stateChanged(d->state); + } + + d->pendingClose = false; + d->socketEngine->setReadNotificationEnabled(true); + d->localPort = d->socketEngine->localPort(); + d->peerPort = d->socketEngine->peerPort(); + d->localAddress = d->socketEngine->localAddress(); + d->peerAddress = d->socketEngine->peerAddress(); + d->cachedSocketDescriptor = socketDescriptor; + + return true; +} + +/*! + \since 4.6 + Sets the given \a option to the value described by \a value. + + \sa socketOption() +*/ +void QAbstractSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ +#ifndef QT_NO_OPENSSL + if (QSslSocket *sslSocket = qobject_cast<QSslSocket*>(this)) { + sslSocket->setSocketOption(option, value); + return; + } +#endif + + if (!d_func()->socketEngine) + return; + + switch (option) { + case LowDelayOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::LowDelayOption, value.toInt()); + break; + + case KeepAliveOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::KeepAliveOption, value.toInt()); + break; + + case MulticastTtlOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::MulticastTtlOption, value.toInt()); + break; + + case MulticastLoopbackOption: + d_func()->socketEngine->setOption(QAbstractSocketEngine::MulticastLoopbackOption, value.toInt()); + break; + } +} + +/*! + \since 4.6 + Returns the value of the \a option option. + + \sa setSocketOption() +*/ +QVariant QAbstractSocket::socketOption(QAbstractSocket::SocketOption option) +{ +#ifndef QT_NO_OPENSSL + if (QSslSocket *sslSocket = qobject_cast<QSslSocket*>(this)) { + return sslSocket->socketOption(option); + } +#endif + + if (!d_func()->socketEngine) + return QVariant(); + + int ret = -1; + switch (option) { + case LowDelayOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::LowDelayOption); + break; + + case KeepAliveOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::KeepAliveOption); + break; + + case MulticastTtlOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::MulticastTtlOption); + break; + case MulticastLoopbackOption: + ret = d_func()->socketEngine->option(QAbstractSocketEngine::MulticastLoopbackOption); + break; + } + if (ret == -1) + return QVariant(); + else + return QVariant(ret); +} + + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +/*! + Waits until the socket is connected, up to \a msecs + milliseconds. If the connection has been established, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 0 + + If msecs is -1, this function will not time out. + + \note This function may wait slightly longer than \a msecs, + depending on the time it takes to complete the host lookup. + + \note Multiple calls to this functions do not accumulate the time. + If the function times out, the connecting process will be aborted. + + \sa connectToHost(), connected() +*/ +bool QAbstractSocket::waitForConnected(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i)", msecs); +#endif + + if (state() == ConnectedState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) already connected", msecs); +#endif + return true; + } + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForConnected(msecs); +#endif + + bool wasPendingClose = d->pendingClose; + d->pendingClose = false; + QElapsedTimer stopWatch; + stopWatch.start(); + + if (d->state == HostLookupState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) doing host name lookup", msecs); +#endif + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; +#ifndef QT_NO_BEARERMANAGEMENT + QSharedPointer<QNetworkSession> networkSession; + QVariant v(property("_q_networksession")); + if (v.isValid()) { + networkSession = qvariant_cast< QSharedPointer<QNetworkSession> >(v); + d->_q_startConnecting(QHostInfoPrivate::fromName(d->hostName, networkSession)); + } else +#endif + d->_q_startConnecting(QHostInfo::fromName(d->hostName)); + } + if (state() == UnconnectedState) + return false; // connect not im progress anymore! + + bool timedOut = true; +#if defined (QABSTRACTSOCKET_DEBUG) + int attempt = 1; +#endif + while (state() == ConnectingState && (msecs == -1 || stopWatch.elapsed() < msecs)) { + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + if (msecs != -1 && timeout > QT_CONNECT_TIMEOUT) + timeout = QT_CONNECT_TIMEOUT; +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) waiting %.2f secs for connection attempt #%i", + msecs, timeout / 1000.0, attempt++); +#endif + timedOut = false; + + if (d->socketEngine && d->socketEngine->waitForWrite(timeout, &timedOut) && !timedOut) { + d->_q_testConnection(); + } else { + d->_q_connectToNextAddress(); + } + } + + if ((timedOut && state() != ConnectedState) || state() == ConnectingState) { + d->socketError = SocketTimeoutError; + d->state = UnconnectedState; + emit stateChanged(d->state); + d->resetSocketLayer(); + setErrorString(tr("Socket operation timed out")); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) == %s", msecs, + state() == ConnectedState ? "true" : "false"); +#endif + if (state() != ConnectedState) + return false; + if (wasPendingClose) + disconnectFromHost(); + return true; +} + +/*! + This function blocks until new data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is new data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + + \sa waitForBytesWritten() +*/ +bool QAbstractSocket::waitForReadyRead(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i)", msecs); +#endif + + // require calling connectToHost() before waitForReadyRead() + if (state() == UnconnectedState) { + /* If all you have is a QIODevice pointer to an abstractsocket, you cannot check + this, so you cannot avoid this warning. */ +// qWarning("QAbstractSocket::waitForReadyRead() is not allowed in UnconnectedState"); + return false; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + Q_ASSERT(d->socketEngine); + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { + if (d->canReadNotification()) + return true; + } + + if (readyToWrite) + d->canWriteNotification(); + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! \reimp + */ +bool QAbstractSocket::waitForBytesWritten(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i)", msecs); +#endif + + // require calling connectToHost() before waitForBytesWritten() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForBytesWritten() is not allowed in UnconnectedState"); + return false; + } + + if (d->writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten calls canReadNotification"); +#endif + if(!d->canReadNotification()) + return false; + } + + + if (readyToWrite) { + if (d->canWriteNotification()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten returns true"); +#endif + return true; + } + } + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 1 + + If msecs is -1, this function will not time out. + + \sa disconnectFromHost(), close() +*/ +bool QAbstractSocket::waitForDisconnected(int msecs) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForDisconnected(msecs); +#endif + + // require calling connectToHost() before waitForDisconnected() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForDisconnected() is not allowed in UnconnectedState"); + return false; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + if (state() == UnconnectedState) + return true; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, state() == ConnectedState, + !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) + d->canReadNotification(); + if (readyToWrite) + d->canWriteNotification(); + + if (state() == UnconnectedState) + return true; + } + return false; +} + +/*! + Aborts the current connection and resets the socket. Unlike disconnectFromHost(), + this function immediately closes the socket, discarding any pending data in the + write buffer. + + \sa disconnectFromHost(), close() +*/ +void QAbstractSocket::abort() +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::abort()"); +#endif + if (d->state == UnconnectedState) + return; +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->abort(); + return; + } +#endif + if (d->connectTimer) { + d->connectTimer->stop(); + delete d->connectTimer; + d->connectTimer = 0; + } + + d->writeBuffer.clear(); + d->abortCalled = true; + close(); +} + +/*! \reimp +*/ +bool QAbstractSocket::isSequential() const +{ + return true; +} + +/*! \reimp + + Returns true if no more data is currently + available for reading; otherwise returns false. + + This function is most commonly used when reading data from the + socket in a loop. For example: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 2 + + \sa bytesAvailable(), readyRead() + */ +bool QAbstractSocket::atEnd() const +{ + return QIODevice::atEnd() && (!isOpen() || d_func()->readBuffer.isEmpty()); +} + +/*! + This function writes as much as possible from the internal write buffer to + the underlying network socket, without blocking. If any data was written, + this function returns true; otherwise false is returned. + + Call this function if you need QAbstractSocket to start sending buffered + data immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QAbstractSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ +// Note! docs copied to QSslSocket::flush() +bool QAbstractSocket::flush() +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; flush() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->flush(); +#endif + Q_CHECK_SOCKETENGINE(false); + return d->flush(); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QAbstractSocket); + + // This is for a buffered QTcpSocket + if (d->isBuffered && d->readBuffer.isEmpty()) + // if we're still connected, return 0 indicating there may be more data in the future + // if we're not connected, return -1 indicating EOF + return d->state == QAbstractSocket::ConnectedState ? qint64(0) : qint64(-1); + + // short cut for a char read if we have something in the buffer + if (maxSize == 1 && !d->readBuffer.isEmpty()) { + *data = d->readBuffer.getChar(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', 1) == 1 [char buffer]", + data, isprint(int(uchar(*data))) ? *data : '?', *data); +#endif + if (d->readBuffer.isEmpty() && d->socketEngine && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + return 1; + } + + // Special case for an Unbuffered QTcpSocket + // Re-filling the buffer. + if (d->socketType == TcpSocket + && !d->isBuffered + && d->readBuffer.size() < maxSize + && d->readBufferMaxSize > 0 + && maxSize < d->readBufferMaxSize + && d->socketEngine + && d->socketEngine->isValid()) { + // Our buffer is empty and a read() was requested for a byte amount that is smaller + // than the readBufferMaxSize. This means that we should fill our buffer since we want + // such small reads come from the buffer and not always go to the costly socket engine read() + qint64 bytesToRead = d->socketEngine->bytesAvailable(); + if (bytesToRead > 0) { + char *ptr = d->readBuffer.reserve(bytesToRead); + qint64 readBytes = d->socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + d->readBuffer.chop(bytesToRead); + } else { + d->readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); + } + } + } + + // First try to satisfy the read from the buffer + qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + d->readBuffer.nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + } + + if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + + if (readSoFar > 0) { + // we read some data from buffer. + // Just return, readyRead will be emitted again +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', %lli) == %lli [buffer]", + data, isprint(int(uchar(*data))) ? *data : '?', *data, maxSize, readSoFar); +#endif + + if (d->readBuffer.isEmpty() && d->socketEngine) + d->socketEngine->setReadNotificationEnabled(true); + return readSoFar; + } + + // This code path is for Unbuffered QTcpSocket or for connected UDP + + if (!d->isBuffered) { + if (!d->socketEngine) + return -1; // no socket engine is probably EOF + if (!d->socketEngine->isValid()) + return -1; // This is for unbuffered TCP when we already had been disconnected + if (d->state != QAbstractSocket::ConnectedState) + return -1; // This is for unbuffered TCP if we're not connected yet + qint64 readBytes = d->socketEngine->read(data, maxSize); + if (readBytes == -2) { + // -2 from the engine means no bytes available (EAGAIN) so read more later + return 0; + } else if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + d->resetSocketLayer(); + d->state = QAbstractSocket::UnconnectedState; + } else if (!d->socketEngine->isReadNotificationEnabled()) { + // Only do this when there was no error + d->socketEngine->setReadNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [engine]", + data, qt_prettyDebug(data, 32, readBytes).data(), maxSize, + readBytes); +#endif + return readBytes; + } + + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld [unreachable]", + data, qt_prettyDebug(data, qMin<qint64>(32, readSoFar), readSoFar).data(), + maxSize, readSoFar); +#endif + return readSoFar; +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readLineData(char *data, qint64 maxlen) +{ + return QIODevice::readLineData(data, maxlen); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::writeData(const char *data, qint64 size) +{ + Q_D(QAbstractSocket); + if (d->state == QAbstractSocket::UnconnectedState) { + d->socketError = QAbstractSocket::UnknownSocketError; + setErrorString(tr("Socket is not connected")); + return -1; + } + + if (!d->isBuffered && d->socketType == TcpSocket && d->writeBuffer.isEmpty()) { + // This code is for the new Unbuffered QTcpSocket use case + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return written; + } else if (written < size) { + // Buffer what was not written yet + char *ptr = d->writeBuffer.reserve(size - written); + memcpy(ptr, data + written, size - written); + if (d->socketEngine) + d->socketEngine->setWriteNotificationEnabled(true); + } + return size; // size=actually written + what has been buffered + } else if (!d->isBuffered && d->socketType != TcpSocket) { + // This is for a QUdpSocket that was connect()ed + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + } else if (!d->writeBuffer.isEmpty()) { + d->socketEngine->setWriteNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + if (written >= 0) + emit bytesWritten(written); + return written; + } + + // This is the code path for normal buffered QTcpSocket or + // unbuffered QTcpSocket when there was already something in the + // write buffer and therefore we could not do a direct engine write. + // We just write to our write buffer and enable the write notifier + // The write notifier then flush()es the buffer. + + char *ptr = d->writeBuffer.reserve(size); + if (size == 1) + *ptr = *data; + else + memcpy(ptr, data, size); + + qint64 written = size; + + if (d->socketEngine && !d->writeBuffer.isEmpty()) + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + return written; +} + +/*! + \since 4.1 + + Sets the port on the local side of a connection to \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local port of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalAddress(), setPeerPort() +*/ +void QAbstractSocket::setLocalPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->localPort = port; +} + +/*! + \since 4.1 + + Sets the address on the local side of a connection to + \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local address of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalPort(), setPeerAddress() +*/ +void QAbstractSocket::setLocalAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->localAddress = address; +} + +/*! + \since 4.1 + + Sets the port of the remote side of the connection to + \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerPort(), setPeerAddress(), setLocalPort() +*/ +void QAbstractSocket::setPeerPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->peerPort = port; +} + +/*! + \since 4.1 + + Sets the address of the remote side of the connection + to \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerAddress(), setPeerPort(), setLocalAddress() +*/ +void QAbstractSocket::setPeerAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->peerAddress = address; +} + +/*! + \since 4.1 + + Sets the host name of the remote peer to \a name. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerName() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerName() +*/ +void QAbstractSocket::setPeerName(const QString &name) +{ + Q_D(QAbstractSocket); + d->peerName = name; +} + +/*! + Closes the I/O device for the socket, disconnects the socket's connection with the + host, closes the socket, and resets the name, address, port number and underlying + socket descriptor. + + See QIODevice::close() for a description of the actions that occur when an I/O + device is closed. + + \sa abort() +*/ +void QAbstractSocket::close() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::close()"); +#endif + QIODevice::close(); + if (d->state != UnconnectedState) { + d->closeCalled = true; + disconnectFromHost(); + } + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName.clear(); + d->cachedSocketDescriptor = -1; +} + +/*! + Attempts to close the socket. If there is pending data waiting to + be written, QAbstractSocket will enter ClosingState and wait + until all data has been written. Eventually, it will enter + UnconnectedState and emit the disconnected() signal. + + \sa connectToHost() +*/ +void QAbstractSocket::disconnectFromHost() +{ + QMetaObject::invokeMethod(this, "disconnectFromHostImplementation", + Qt::DirectConnection); +} + +/*! + \since 4.1 + + Contains the implementation of disconnectFromHost(). +*/ +void QAbstractSocket::disconnectFromHostImplementation() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost()"); +#endif + + if (d->state == UnconnectedState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() was called on an unconnected socket"); +#endif + return; + } + + if (!d->abortCalled && (d->state == ConnectingState || d->state == HostLookupState)) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() but we're still connecting"); +#endif + d->pendingClose = true; + return; + } + +#ifdef QT3_SUPPORT + emit connectionClosed(); // compat signal +#endif + + // Disable and delete read notification + if (d->socketEngine) + d->socketEngine->setReadNotificationEnabled(false); + + if (d->abortCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() aborting immediately"); +#endif + if (d->state == HostLookupState) { + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + } + } else { + // Perhaps emit closing() + if (d->state != ClosingState) { + d->state = ClosingState; +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() emits stateChanged()(ClosingState)"); +#endif + emit stateChanged(d->state); + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() return from delayed close"); +#endif + } + + // Wait for pending data to be written. + if (d->socketEngine && d->socketEngine->isValid() && (d->writeBuffer.size() > 0 + || d->socketEngine->bytesToWrite() > 0)) { + // hack: when we are waiting for the socket engine to write bytes (only + // possible when using Socks5 or HTTP socket engine), then close + // anyway after 2 seconds. This is to prevent a timeout on Mac, where we + // sometimes just did not get the write notifier from the underlying + // CFSocket and no progress was made. + if (d->writeBuffer.size() == 0 && d->socketEngine->bytesToWrite() > 0) { + if (!d->disconnectTimer) { + d->disconnectTimer = new QTimer(this); + connect(d->disconnectTimer, SIGNAL(timeout()), this, + SLOT(_q_forceDisconnect()), Qt::DirectConnection); + } + if (!d->disconnectTimer->isActive()) + d->disconnectTimer->start(2000); + } + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() delaying disconnect"); +#endif + return; + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnecting immediately"); +#endif + } + } + + SocketState previousState = d->state; + d->resetSocketLayer(); + d->state = UnconnectedState; + emit stateChanged(d->state); + emit readChannelFinished(); // we got an EOF + +#ifdef QT3_SUPPORT + emit delayedCloseFinished(); // compat signal +#endif + // only emit disconnected if we were connected before + if (previousState == ConnectedState || previousState == ClosingState) + emit disconnected(); + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnected!"); +#endif + + if (d->closeCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() closed!"); +#endif + d->readBuffer.clear(); + d->writeBuffer.clear(); + QIODevice::close(); + } +} + +/*! + Returns the size of the internal read buffer. This limits the + amount of data that the client can receive before you call read() + or readAll(). + + A read buffer size of 0 (the default) means that the buffer has + no size limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ +qint64 QAbstractSocket::readBufferSize() const +{ + return d_func()->readBufferMaxSize; +} + +/*! + Sets the size of QAbstractSocket's internal read buffer to be \a + size bytes. + + If the buffer size is limited to a certain size, QAbstractSocket + won't buffer more than this size of data. Exceptionally, a buffer + size of 0 means that the read buffer is unlimited and all + incoming data is buffered. This is the default. + + This option is useful if you only read the data at certain points + in time (e.g., in a real-time streaming application) or if you + want to protect your socket against receiving too much data, + which may eventually cause your application to run out of memory. + + Only QTcpSocket uses QAbstractSocket's internal buffer; QUdpSocket + does not use any buffering at all, but rather relies on the + implicit buffering provided by the operating system. + Because of this, calling this function on QUdpSocket has no + effect. + + \sa readBufferSize(), read() +*/ +void QAbstractSocket::setReadBufferSize(qint64 size) +{ + Q_D(QAbstractSocket); + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; setReadBufferSize() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->setReadBufferSize(size); + return; + } +#endif + + if (d->readBufferMaxSize == size) + return; + d->readBufferMaxSize = size; + if (!d->readSocketNotifierCalled && d->socketEngine) { + // ensure that the read notification is enabled if we've now got + // room in the read buffer + // but only if we're not inside canReadNotification -- that will take care on its own + if ((size == 0 || d->readBuffer.size() < size) && d->state == QAbstractSocket::ConnectedState) // Do not change the notifier unless we are connected. + d->socketEngine->setReadNotificationEnabled(true); + } +} + +/*! + Returns the state of the socket. + + \sa error() +*/ +QAbstractSocket::SocketState QAbstractSocket::state() const +{ + return d_func()->state; +} + +/*! + Sets the state of the socket to \a state. + + \sa state() +*/ +void QAbstractSocket::setSocketState(SocketState state) +{ + d_func()->state = state; +} + +/*! + Returns the socket type (TCP, UDP, or other). + + \sa QTcpSocket, QUdpSocket +*/ +QAbstractSocket::SocketType QAbstractSocket::socketType() const +{ + return d_func()->socketType; +} + +/*! + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ +QAbstractSocket::SocketError QAbstractSocket::error() const +{ + return d_func()->socketError; +} + +/*! + Sets the type of error that last occurred to \a socketError. + + \sa setSocketState(), setErrorString() +*/ +void QAbstractSocket::setSocketError(SocketError socketError) +{ + d_func()->socketError = socketError; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 3 + + The default value for the proxy is QNetworkProxy::DefaultProxy, + which means the socket will use the application settings: if a + proxy is set with QNetworkProxy::setApplicationProxy, it will use + that; otherwise, if a factory is set with + QNetworkProxyFactory::setApplicationProxyFactory, it will query + that factory with type QNetworkProxyQuery::TcpSocket. + + \sa proxy(), QNetworkProxy, QNetworkProxyFactory::queryProxy() +*/ +void QAbstractSocket::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QAbstractSocket); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used, which means this + socket will query the default proxy settings for the application. + + \sa setProxy(), QNetworkProxy, QNetworkProxyFactory +*/ +QNetworkProxy QAbstractSocket::proxy() const +{ + Q_D(const QAbstractSocket); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +#ifdef QT3_SUPPORT +/*! + \enum QAbstractSocket::Error + \compat + + Use QAbstractSocket::SocketError instead. + + \value ErrConnectionRefused Use QAbstractSocket::ConnectionRefusedError instead. + \value ErrHostNotFound Use QAbstractSocket::HostNotFoundError instead. + \value ErrSocketRead Use QAbstractSocket::UnknownSocketError instead. +*/ + +/*! + \typedef QAbstractSocket::State + \compat + + Use QAbstractSocket::SocketState instead. + + \table + \header \o Qt 3 enum value \o Qt 4 enum value + \row \o \c Idle \o \l UnconnectedState + \row \o \c HostLookup \o \l HostLookupState + \row \o \c Connecting \o \l ConnectingState + \row \o \c Connected \o \l ConnectedState + \row \o \c Closing \o \l ClosingState + \row \o \c Connection \o \l ConnectedState + \endtable +*/ + +/*! + \fn int QAbstractSocket::socket() const + + Use socketDescriptor() instead. +*/ + +/*! + \fn void QAbstractSocket::setSocket(int socket) + + Use setSocketDescriptor() instead. +*/ + +/*! + \fn Q_ULONG QAbstractSocket::waitForMore(int msecs, bool *timeout = 0) const + + Use waitForReadyRead() instead. + + \oldcode + bool timeout; + Q_ULONG numBytes = socket->waitForMore(30000, &timeout); + \newcode + qint64 numBytes = 0; + if (socket->waitForReadyRead(msecs)) + numBytes = socket->bytesAvailable(); + bool timeout = (error() == QAbstractSocket::SocketTimeoutError); + \endcode + + \sa waitForReadyRead(), bytesAvailable(), error(), SocketTimeoutError +*/ + +/*! + \fn void QAbstractSocket::connectionClosed() + + Use disconnected() instead. +*/ + +/*! + \fn void QAbstractSocket::delayedCloseFinished() + + Use disconnected() instead. +*/ +#endif // QT3_SUPPORT + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ConnectionRefusedError: + debug << "QAbstractSocket::ConnectionRefusedError"; + break; + case QAbstractSocket::RemoteHostClosedError: + debug << "QAbstractSocket::RemoteHostClosedError"; + break; + case QAbstractSocket::HostNotFoundError: + debug << "QAbstractSocket::HostNotFoundError"; + break; + case QAbstractSocket::SocketAccessError: + debug << "QAbstractSocket::SocketAccessError"; + break; + case QAbstractSocket::SocketResourceError: + debug << "QAbstractSocket::SocketResourceError"; + break; + case QAbstractSocket::SocketTimeoutError: + debug << "QAbstractSocket::SocketTimeoutError"; + break; + case QAbstractSocket::DatagramTooLargeError: + debug << "QAbstractSocket::DatagramTooLargeError"; + break; + case QAbstractSocket::NetworkError: + debug << "QAbstractSocket::NetworkError"; + break; + case QAbstractSocket::AddressInUseError: + debug << "QAbstractSocket::AddressInUseError"; + break; + case QAbstractSocket::SocketAddressNotAvailableError: + debug << "QAbstractSocket::SocketAddressNotAvailableError"; + break; + case QAbstractSocket::UnsupportedSocketOperationError: + debug << "QAbstractSocket::UnsupportedSocketOperationError"; + break; + case QAbstractSocket::UnfinishedSocketOperationError: + debug << "QAbstractSocket::UnfinishedSocketOperationError"; + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + debug << "QAbstractSocket::ProxyAuthenticationRequiredError"; + break; + case QAbstractSocket::UnknownSocketError: + debug << "QAbstractSocket::UnknownSocketError"; + break; + case QAbstractSocket::ProxyConnectionRefusedError: + debug << "QAbstractSocket::ProxyConnectionRefusedError"; + break; + case QAbstractSocket::ProxyConnectionClosedError: + debug << "QAbstractSocket::ProxyConnectionClosedError"; + break; + case QAbstractSocket::ProxyConnectionTimeoutError: + debug << "QAbstractSocket::ProxyConnectionTimeoutError"; + break; + case QAbstractSocket::ProxyNotFoundError: + debug << "QAbstractSocket::ProxyNotFoundError"; + break; + case QAbstractSocket::ProxyProtocolError: + debug << "QAbstractSocket::ProxyProtocolError"; + break; + default: + debug << "QAbstractSocket::SocketError(" << int(error) << ')'; + break; + } + return debug; +} + +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::UnconnectedState: + debug << "QAbstractSocket::UnconnectedState"; + break; + case QAbstractSocket::HostLookupState: + debug << "QAbstractSocket::HostLookupState"; + break; + case QAbstractSocket::ConnectingState: + debug << "QAbstractSocket::ConnectingState"; + break; + case QAbstractSocket::ConnectedState: + debug << "QAbstractSocket::ConnectedState"; + break; + case QAbstractSocket::BoundState: + debug << "QAbstractSocket::BoundState"; + break; + case QAbstractSocket::ListeningState: + debug << "QAbstractSocket::ListeningState"; + break; + case QAbstractSocket::ClosingState: + debug << "QAbstractSocket::ClosingState"; + break; + default: + debug << "QAbstractSocket::SocketState(" << int(state) << ')'; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qabstractsocket.cpp" diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h new file mode 100644 index 0000000000..24f5478911 --- /dev/null +++ b/src/network/socket/qabstractsocket.h @@ -0,0 +1,260 @@ +/**************************************************************************** +** +** 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 QABSTRACTSOCKET_H +#define QABSTRACTSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qobject.h> +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddress; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QAbstractSocketPrivate; +class QAuthenticator; + +class Q_NETWORK_EXPORT QAbstractSocket : public QIODevice +{ + Q_OBJECT + Q_ENUMS(SocketType NetworkLayerProtocol SocketError SocketState SocketOption) +public: + enum SocketType { + TcpSocket, + UdpSocket, + UnknownSocketType = -1 + }; + enum NetworkLayerProtocol { + IPv4Protocol, + IPv6Protocol, + UnknownNetworkLayerProtocol = -1 + }; + enum SocketError { + ConnectionRefusedError, + RemoteHostClosedError, + HostNotFoundError, + SocketAccessError, + SocketResourceError, + SocketTimeoutError, /* 5 */ + DatagramTooLargeError, + NetworkError, + AddressInUseError, + SocketAddressNotAvailableError, + UnsupportedSocketOperationError, /* 10 */ + UnfinishedSocketOperationError, + ProxyAuthenticationRequiredError, + SslHandshakeFailedError, + ProxyConnectionRefusedError, + ProxyConnectionClosedError, /* 15 */ + ProxyConnectionTimeoutError, + ProxyNotFoundError, + ProxyProtocolError, + + UnknownSocketError = -1 + }; + enum SocketState { + UnconnectedState, + HostLookupState, + ConnectingState, + ConnectedState, + BoundState, + ListeningState, + ClosingState +#ifdef QT3_SUPPORT + , + Idle = UnconnectedState, + HostLookup = HostLookupState, + Connecting = ConnectingState, + Connected = ConnectedState, + Closing = ClosingState, + Connection = ConnectedState +#endif + }; + enum SocketOption { + LowDelayOption, // TCP_NODELAY + KeepAliveOption, // SO_KEEPALIVE + MulticastTtlOption, // IP_MULTICAST_TTL + MulticastLoopbackOption // IP_MULTICAST_LOOPBACK + }; + + QAbstractSocket(SocketType socketType, QObject *parent); + virtual ~QAbstractSocket(); + + // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual. + void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHost(); + + bool isValid() const; + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + bool canReadLine() const; + + quint16 localPort() const; + QHostAddress localAddress() const; + quint16 peerPort() const; + QHostAddress peerAddress() const; + QString peerName() const; + + // ### Qt 5: Make setReadBufferSize() virtual + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + void abort(); + + // ### Qt 5: Make socketDescriptor() and setSocketDescriptor() virtual. + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor, SocketState state = ConnectedState, + OpenMode openMode = ReadWrite); + + // ### Qt 5: Make virtual? + void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); + QVariant socketOption(QAbstractSocket::SocketOption option); + + SocketType socketType() const; + SocketState state() const; + SocketError error() const; + + // from QIODevice + void close(); + bool isSequential() const; + bool atEnd() const; + bool flush(); + + // for synchronous access + // ### Qt 5: Make waitForConnected() and waitForDisconnected() virtual. + bool waitForConnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +Q_SIGNALS: + void hostFound(); + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState); + void error(QAbstractSocket::SocketError); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +protected Q_SLOTS: + void connectToHostImplementation(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHostImplementation(); + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 readLineData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + + void setSocketState(SocketState state); + void setSocketError(SocketError socketError); + void setLocalPort(quint16 port); + void setLocalAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerName(const QString &name); + + QAbstractSocket(SocketType socketType, QAbstractSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DECLARE_PRIVATE(QAbstractSocket) + Q_DISABLE_COPY(QAbstractSocket) + + Q_PRIVATE_SLOT(d_func(), void _q_connectToNextAddress()) + Q_PRIVATE_SLOT(d_func(), void _q_startConnecting(const QHostInfo &)) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) + Q_PRIVATE_SLOT(d_func(), void _q_testConnection()) + Q_PRIVATE_SLOT(d_func(), void _q_forceDisconnect()) + +#ifdef QT3_SUPPORT +public: + enum Error { + ErrConnectionRefused = ConnectionRefusedError, + ErrHostNotFound = HostNotFoundError, + ErrSocketRead = UnknownSocketError + }; + inline QT3_SUPPORT int socket() const { return socketDescriptor(); } + inline QT3_SUPPORT void setSocket(int socket) { setSocketDescriptor(socket); } + inline QT3_SUPPORT qulonglong waitForMore(int msecs, bool *timeout = 0) const + { + QAbstractSocket *that = const_cast<QAbstractSocket *>(this); + if (that->waitForReadyRead(msecs)) + return qulonglong(bytesAvailable()); + if (error() == SocketTimeoutError && timeout) + *timeout = true; + return 0; + } + typedef SocketState State; +Q_SIGNALS: + QT_MOC_COMPAT void connectionClosed(); // same as disconnected() + QT_MOC_COMPAT void delayedCloseFinished(); // same as disconnected() + + +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSOCKET_H diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h new file mode 100644 index 0000000000..7662f47fbf --- /dev/null +++ b/src/network/socket/qabstractsocket_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 QABSTRACTSOCKET_P_H +#define QABSTRACTSOCKET_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 QAbstractSocket class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qabstractsocket.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qlist.h" +#include "QtCore/qtimer.h" +#include "private/qringbuffer_p.h" +#include "private/qiodevice_p.h" +#include "private/qabstractsocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QHostInfo; + +class QAbstractSocketPrivate : public QIODevicePrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QAbstractSocket) +public: + QAbstractSocketPrivate(); + virtual ~QAbstractSocketPrivate(); + + // from QAbstractSocketEngineReceiver + inline void readNotification() { canReadNotification(); } + inline void writeNotification() { canWriteNotification(); } + inline void exceptionNotification() {} + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) { + Q_Q(QAbstractSocket); + q->proxyAuthenticationRequired(proxy, authenticator); + } +#endif + + bool canReadNotification(); + bool canWriteNotification(); + + // slots + void _q_connectToNextAddress(); + void _q_startConnecting(const QHostInfo &hostInfo); + void _q_testConnection(); + void _q_abortConnectionAttempt(); + void _q_forceDisconnect(); + + bool readSocketNotifierCalled; + bool readSocketNotifierState; + bool readSocketNotifierStateSet; + + bool emittedReadyRead; + bool emittedBytesWritten; + + bool abortCalled; + bool closeCalled; + bool pendingClose; + + QString hostName; + quint16 port; + QHostAddress host; + QList<QHostAddress> addresses; + + quint16 localPort; + quint16 peerPort; + QHostAddress localAddress; + QHostAddress peerAddress; + QString peerName; + + QAbstractSocketEngine *socketEngine; + int cachedSocketDescriptor; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy proxyInUse; + void resolveProxy(const QString &hostName, quint16 port); +#else + inline void resolveProxy(const QString &, quint16) { } +#endif + inline void resolveProxy(quint16 port) { resolveProxy(QString(), port); } + + void resetSocketLayer(); + bool flush(); + + bool initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol); + void startConnectingByName(const QString &host); + void fetchConnectionParameters(); + void setupSocketNotifiers(); + bool readFromSocket(); + + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + QRingBuffer writeBuffer; + + bool isBuffered; + int blockingTimeout; + + QTimer *connectTimer; + QTimer *disconnectTimer; + int connectTimeElapsed; + + int hostLookupId; + + QAbstractSocket::SocketType socketType; + QAbstractSocket::SocketState state; + + QAbstractSocket::SocketError socketError; + + bool prePauseReadSocketNotifierState; + bool prePauseWriteSocketNotifierState; + bool prePauseExceptionSocketNotifierState; + static void pauseSocketNotifiers(QAbstractSocket*); + static void resumeSocketNotifiers(QAbstractSocket*); + static QAbstractSocketEngine* getSocketEngine(QAbstractSocket*); +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKET_P_H diff --git a/src/network/socket/qabstractsocketengine.cpp b/src/network/socket/qabstractsocketengine.cpp new file mode 100644 index 0000000000..c29f936923 --- /dev/null +++ b/src/network/socket/qabstractsocketengine.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** 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 "qabstractsocketengine_p.h" + +#ifdef Q_OS_SYMBIAN +#include "qsymbiansocketengine_p.h" +#else +#include "qnativesocketengine_p.h" +#endif + +#include "qmutex.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QSocketEngineHandlerList : public QList<QSocketEngineHandler*> +{ +public: + QMutex mutex; +}; + +Q_GLOBAL_STATIC(QSocketEngineHandlerList, socketHandlers) + +QSocketEngineHandler::QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->prepend(this); +} + +QSocketEngineHandler::~QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->removeAll(this); +} + +QAbstractSocketEnginePrivate::QAbstractSocketEnginePrivate() + : socketError(QAbstractSocket::UnknownSocketError) + , hasSetSocketError(false) + , socketErrorString(QLatin1String(QT_TRANSLATE_NOOP(QSocketLayer, "Unknown error"))) + , socketState(QAbstractSocket::UnconnectedState) + , socketType(QAbstractSocket::UnknownSocketType) + , socketProtocol(QAbstractSocket::UnknownNetworkLayerProtocol) + , localPort(0) + , peerPort(0) + , receiver(0) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QObject *parent) + : QObject(*new QAbstractSocketEnginePrivate(), parent) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent) + : QObject(dd, parent) +{ +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent) +{ +#ifndef QT_NO_NETWORKPROXY + // proxy type must have been resolved by now + if (proxy.type() == QNetworkProxy::DefaultProxy) + return 0; +#endif + + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketType, proxy, parent)) + return ret; + } + +#ifndef QT_NO_NETWORKPROXY + // only NoProxy can have reached here + if (proxy.type() != QNetworkProxy::NoProxy) + return 0; +#endif + +#ifdef Q_OS_SYMBIAN + return new QSymbianSocketEngine(parent); +#else + return new QNativeSocketEngine(parent); +#endif +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(int socketDescripter, QObject *parent) +{ + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketDescripter, parent)) + return ret; + } +#ifdef Q_OS_SYMBIAN + return new QSymbianSocketEngine(parent); +#else + return new QNativeSocketEngine(parent); +#endif +} + +QAbstractSocket::SocketError QAbstractSocketEngine::error() const +{ + return d_func()->socketError; +} + +QString QAbstractSocketEngine::errorString() const +{ + return d_func()->socketErrorString; +} + +void QAbstractSocketEngine::setError(QAbstractSocket::SocketError error, const QString &errorString) const +{ + Q_D(const QAbstractSocketEngine); + d->socketError = error; + d->socketErrorString = errorString; +} + +void QAbstractSocketEngine::setReceiver(QAbstractSocketEngineReceiver *receiver) +{ + d_func()->receiver = receiver; +} + +void QAbstractSocketEngine::readNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->readNotification(); +} + +void QAbstractSocketEngine::writeNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->writeNotification(); +} + +void QAbstractSocketEngine::exceptionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->exceptionNotification(); +} + +void QAbstractSocketEngine::connectionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->connectionNotification(); +} + +#ifndef QT_NO_NETWORKPROXY +void QAbstractSocketEngine::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->proxyAuthenticationRequired(proxy, authenticator); +} +#endif + + +QAbstractSocket::SocketState QAbstractSocketEngine::state() const +{ + return d_func()->socketState; +} + +void QAbstractSocketEngine::setState(QAbstractSocket::SocketState state) +{ + d_func()->socketState = state; +} + +QAbstractSocket::SocketType QAbstractSocketEngine::socketType() const +{ + return d_func()->socketType; +} + +void QAbstractSocketEngine::setSocketType(QAbstractSocket::SocketType socketType) +{ + d_func()->socketType = socketType; +} + +QAbstractSocket::NetworkLayerProtocol QAbstractSocketEngine::protocol() const +{ + return d_func()->socketProtocol; +} + +void QAbstractSocketEngine::setProtocol(QAbstractSocket::NetworkLayerProtocol protocol) +{ + d_func()->socketProtocol = protocol; +} + +QHostAddress QAbstractSocketEngine::localAddress() const +{ + return d_func()->localAddress; +} + +void QAbstractSocketEngine::setLocalAddress(const QHostAddress &address) +{ + d_func()->localAddress = address; +} + +quint16 QAbstractSocketEngine::localPort() const +{ + return d_func()->localPort; +} + +void QAbstractSocketEngine::setLocalPort(quint16 port) +{ + d_func()->localPort = port; +} + +QHostAddress QAbstractSocketEngine::peerAddress() const +{ + return d_func()->peerAddress; +} + +void QAbstractSocketEngine::setPeerAddress(const QHostAddress &address) +{ + d_func()->peerAddress = address; +} + +quint16 QAbstractSocketEngine::peerPort() const +{ + return d_func()->peerPort; +} + +void QAbstractSocketEngine::setPeerPort(quint16 port) +{ + d_func()->peerPort = port; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h new file mode 100644 index 0000000000..ee6dad60d8 --- /dev/null +++ b/src/network/socket/qabstractsocketengine_p.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** 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 QABSTRACTSOCKETENGINE_P_H +#define QABSTRACTSOCKETENGINE_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 "QtNetwork/qhostaddress.h" +#include "QtNetwork/qabstractsocket.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QAuthenticator; +class QAbstractSocketEnginePrivate; +#ifndef QT_NO_NETWORKINTERFACE +class QNetworkInterface; +#endif +class QNetworkProxy; + +class QAbstractSocketEngineReceiver { +public: + virtual ~QAbstractSocketEngineReceiver(){} + virtual void readNotification()= 0; + virtual void writeNotification()= 0; + virtual void exceptionNotification()= 0; + virtual void connectionNotification()= 0; +#ifndef QT_NO_NETWORKPROXY + virtual void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)= 0; +#endif +}; + +class Q_AUTOTEST_EXPORT QAbstractSocketEngine : public QObject +{ + Q_OBJECT +public: + + static QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &, QObject *parent); + static QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); + + QAbstractSocketEngine(QObject *parent = 0); + + enum SocketOption { + NonBlockingSocketOption, + BroadcastSocketOption, + ReceiveBufferSocketOption, + SendBufferSocketOption, + AddressReusable, + BindExclusively, + ReceiveOutOfBandData, + LowDelayOption, + KeepAliveOption, + MulticastTtlOption, + MulticastLoopbackOption + }; + + virtual bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol) = 0; + + virtual bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState) = 0; + + virtual int socketDescriptor() const = 0; + + virtual bool isValid() const = 0; + + virtual bool connectToHost(const QHostAddress &address, quint16 port) = 0; + virtual bool connectToHostByName(const QString &name, quint16 port) = 0; + virtual bool bind(const QHostAddress &address, quint16 port) = 0; + virtual bool listen() = 0; + virtual int accept() = 0; + virtual void close() = 0; + + virtual qint64 bytesAvailable() const = 0; + + virtual qint64 read(char *data, qint64 maxlen) = 0; + virtual qint64 write(const char *data, qint64 len) = 0; + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + virtual bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) = 0; + virtual bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) = 0; + virtual QNetworkInterface multicastInterface() const = 0; + virtual bool setMulticastInterface(const QNetworkInterface &iface) = 0; +#endif // QT_NO_NETWORKINTERFACE + + virtual qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0) = 0; + virtual qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port) = 0; + virtual bool hasPendingDatagrams() const = 0; + virtual qint64 pendingDatagramSize() const = 0; +#endif // QT_NO_UDPSOCKET + + virtual qint64 bytesToWrite() const = 0; + + virtual int option(SocketOption option) const = 0; + virtual bool setOption(SocketOption option, int value) = 0; + + virtual bool waitForRead(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForWrite(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0) = 0; + + QAbstractSocket::SocketError error() const; + QString errorString() const; + QAbstractSocket::SocketState state() const; + QAbstractSocket::SocketType socketType() const; + QAbstractSocket::NetworkLayerProtocol protocol() const; + + QHostAddress localAddress() const; + quint16 localPort() const; + QHostAddress peerAddress() const; + quint16 peerPort() const; + + virtual bool isReadNotificationEnabled() const = 0; + virtual void setReadNotificationEnabled(bool enable) = 0; + virtual bool isWriteNotificationEnabled() const = 0; + virtual void setWriteNotificationEnabled(bool enable) = 0; + virtual bool isExceptionNotificationEnabled() const = 0; + virtual void setExceptionNotificationEnabled(bool enable) = 0; + +public Q_SLOTS: + void readNotification(); + void writeNotification(); + void exceptionNotification(); + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +public: + void setReceiver(QAbstractSocketEngineReceiver *receiver); +protected: + QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent = 0); + + void setError(QAbstractSocket::SocketError error, const QString &errorString) const; + void setState(QAbstractSocket::SocketState state); + void setSocketType(QAbstractSocket::SocketType socketType); + void setProtocol(QAbstractSocket::NetworkLayerProtocol protocol); + void setLocalAddress(const QHostAddress &address); + void setLocalPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + +private: + Q_DECLARE_PRIVATE(QAbstractSocketEngine) + Q_DISABLE_COPY(QAbstractSocketEngine) +}; + +class QAbstractSocketEnginePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractSocketEngine) +public: + QAbstractSocketEnginePrivate(); + + mutable QAbstractSocket::SocketError socketError; + mutable bool hasSetSocketError; + mutable QString socketErrorString; + QAbstractSocket::SocketState socketState; + QAbstractSocket::SocketType socketType; + QAbstractSocket::NetworkLayerProtocol socketProtocol; + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QAbstractSocketEngineReceiver *receiver; +}; + + +class Q_AUTOTEST_EXPORT QSocketEngineHandler +{ +protected: + QSocketEngineHandler(); + virtual ~QSocketEngineHandler(); + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent) = 0; + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent) = 0; + +private: + friend class QAbstractSocketEngine; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKETENGINE_P_H diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp new file mode 100644 index 0000000000..7846056221 --- /dev/null +++ b/src/network/socket/qhttpsocketengine.cpp @@ -0,0 +1,824 @@ +/**************************************************************************** +** +** 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 "qhttpsocketengine_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" +#include "qurl.h" +#include "qhttp.h" +#include "qelapsedtimer.h" +#include "qnetworkinterface.h" + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define DEBUG + +QHttpSocketEngine::QHttpSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QHttpSocketEnginePrivate, parent) +{ +} + +QHttpSocketEngine::~QHttpSocketEngine() +{ +} + +bool QHttpSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QHttpSocketEngine); + if (type != QAbstractSocket::TcpSocket) + return false; + + setProtocol(protocol); + setSocketType(type); + d->socket = new QTcpSocket(this); +#ifndef QT_NO_BEARERMANAGEMENT + d->socket->setProperty("_q_networkSession", property("_q_networkSession")); +#endif + + // Explicitly disable proxying on the proxy socket itself to avoid + // unwanted recursion. + d->socket->setProxy(QNetworkProxy::NoProxy); + + // Intercept all the signals. + connect(d->socket, SIGNAL(connected()), + this, SLOT(slotSocketConnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(disconnected()), + this, SLOT(slotSocketDisconnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(readyRead()), + this, SLOT(slotSocketReadNotification()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(bytesWritten(qint64)), + this, SLOT(slotSocketBytesWritten()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(slotSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + connect(d->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(slotSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + return true; +} + +bool QHttpSocketEngine::initialize(int, QAbstractSocket::SocketState) +{ + return false; +} + +void QHttpSocketEngine::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QHttpSocketEngine); + d->proxy = proxy; + QString user = proxy.user(); + if (!user.isEmpty()) + d->authenticator.setUser(user); + QString password = proxy.password(); + if (!password.isEmpty()) + d->authenticator.setPassword(password); +} + +int QHttpSocketEngine::socketDescriptor() const +{ + Q_D(const QHttpSocketEngine); + return d->socket ? d->socket->socketDescriptor() : 0; +} + +bool QHttpSocketEngine::isValid() const +{ + Q_D(const QHttpSocketEngine); + return d->socket; +} + +bool QHttpSocketEngine::connectInternal() +{ + Q_D(QHttpSocketEngine); + + // If the handshake is done, enter ConnectedState state and return true. + if (d->state == Connected) { + qWarning("QHttpSocketEngine::connectToHost: called when already connected"); + setState(QAbstractSocket::ConnectedState); + return true; + } + + if (d->state == ConnectSent && d->socketState != QAbstractSocket::ConnectedState) + setState(QAbstractSocket::UnconnectedState); + + // Handshake isn't done. If unconnected, start connecting. + if (d->state == None && d->socket->state() == QAbstractSocket::UnconnectedState) { + setState(QAbstractSocket::ConnectingState); + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } + + // If connected (might happen right away, at least for localhost services + // on some BSD systems), there might already be bytes available. + if (bytesAvailable()) + slotSocketReadNotification(); + + return d->socketState == QAbstractSocket::ConnectedState; +} + +bool QHttpSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QHttpSocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +bool QHttpSocketEngine::bind(const QHostAddress &, quint16) +{ + return false; +} + +bool QHttpSocketEngine::listen() +{ + return false; +} + +int QHttpSocketEngine::accept() +{ + return 0; +} + +void QHttpSocketEngine::close() +{ + Q_D(QHttpSocketEngine); + if (d->socket) { + d->socket->close(); + delete d->socket; + d->socket = 0; + } +} + +qint64 QHttpSocketEngine::bytesAvailable() const +{ + Q_D(const QHttpSocketEngine); + return d->readBuffer.size() + (d->socket ? d->socket->bytesAvailable() : 0); +} + +qint64 QHttpSocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QHttpSocketEngine); + qint64 bytesRead = d->socket->read(data, maxlen); + + if (d->socket->state() == QAbstractSocket::UnconnectedState + && d->socket->bytesAvailable() == 0) { + emitReadNotification(); + } + + if (bytesRead == -1) { + // If nothing has been read so far, and the direct socket read + // failed, return the socket's error. Otherwise, fall through and + // return as much as we read so far. + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } + return bytesRead; +} + +qint64 QHttpSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QHttpSocketEngine); + return d->socket->write(data, len); +} + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE +bool QHttpSocketEngine::joinMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +bool QHttpSocketEngine::leaveMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +QNetworkInterface QHttpSocketEngine::multicastInterface() const +{ + return QNetworkInterface(); +} + +bool QHttpSocketEngine::setMulticastInterface(const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} +#endif // QT_NO_NETWORKINTERFACE + +qint64 QHttpSocketEngine::readDatagram(char *, qint64, QHostAddress *, + quint16 *) +{ + return 0; +} + +qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QHostAddress &, + quint16) +{ + return 0; +} + +bool QHttpSocketEngine::hasPendingDatagrams() const +{ + return false; +} + +qint64 QHttpSocketEngine::pendingDatagramSize() const +{ + return 0; +} +#endif // QT_NO_UDPSOCKET + +qint64 QHttpSocketEngine::bytesToWrite() const +{ + Q_D(const QHttpSocketEngine); + if (d->socket) { + return d->socket->bytesToWrite(); + } else { + return 0; + } +} + +int QHttpSocketEngine::option(SocketOption option) const +{ + Q_D(const QHttpSocketEngine); + if (d->socket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + return d->socket->socketOption(QAbstractSocket::LowDelayOption).toInt(); + if (option == QAbstractSocketEngine::KeepAliveOption) + return d->socket->socketOption(QAbstractSocket::KeepAliveOption).toInt(); + } + return -1; +} + +bool QHttpSocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QHttpSocketEngine); + if (d->socket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + d->socket->setSocketOption(QAbstractSocket::LowDelayOption, value); + if (option == QAbstractSocketEngine::KeepAliveOption) + d->socket->setSocketOption(QAbstractSocket::KeepAliveOption, value); + return true; + } + return false; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + if (!d->socket || d->socket->state() == QAbstractSocket::UnconnectedState) + return false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // Wait for more data if nothing is available. + if (!d->socket->bytesAvailable()) { + if (!d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->socket->state() == QAbstractSocket::UnconnectedState) + return true; + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + // If we're not connected yet, wait until we are, or until an error + // occurs. + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + return true; +} + +bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + // If we're connected, just forward the call. + if (d->state == Connected) { + if (d->socket->bytesToWrite()) { + if (!d->socket->waitForBytesWritten(msecs)) { + if (d->socket->error() == QAbstractSocket::SocketTimeoutError && timedOut) + *timedOut = true; + return false; + } + } + return true; + } + + QElapsedTimer stopWatch; + stopWatch.start(); + + // If we're not connected yet, wait until we are, and until bytes have + // been received (i.e., the socket has connected, we have sent the + // greeting, and then received the response). + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { +// setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + } + + return true; +} + +bool QHttpSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + + if (!checkWrite) { + // Not interested in writing? Then we wait for read notifications. + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + // Interested in writing? Then we wait for write notifications. + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QHttpSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->readNotificationEnabled; +} + +void QHttpSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + if (d->readNotificationEnabled == enable) + return; + + d->readNotificationEnabled = enable; + if (enable) { + // Enabling read notification can trigger a notification. + if (bytesAvailable()) + slotSocketReadNotification(); + } +} + +bool QHttpSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->writeNotificationEnabled; +} + +void QHttpSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->state == Connected && d->socket->state() == QAbstractSocket::ConnectedState) + QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection); +} + +bool QHttpSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->exceptNotificationEnabled; +} + +void QHttpSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->exceptNotificationEnabled = enable; +} + +void QHttpSocketEngine::slotSocketConnected() +{ + Q_D(QHttpSocketEngine); + + // Send the greeting. + const char method[] = "CONNECT "; + QByteArray peerAddress = d->peerName.isEmpty() ? + d->peerAddress.toString().toLatin1() : + QUrl::toAce(d->peerName); + QByteArray path = peerAddress + ':' + QByteArray::number(d->peerPort); + QByteArray data = method; + data += path; + data += " HTTP/1.1\r\n"; + data += "Proxy-Connection: keep-alive\r\n" + "User-Agent: Mozilla/5.0\r\n" + "Host: " + peerAddress + "\r\n"; + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + //qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1); + if (priv && priv->method != QAuthenticatorPrivate::None) { + data += "Proxy-Authorization: " + priv->calculateResponse(method, path); + data += "\r\n"; + } + data += "\r\n"; +// qDebug() << ">>>>>>>> sending request" << this; +// qDebug() << data; +// qDebug() << ">>>>>>>"; + d->socket->write(data); + d->state = ConnectSent; +} + +void QHttpSocketEngine::slotSocketDisconnected() +{ +} + +void QHttpSocketEngine::slotSocketReadNotification() +{ + Q_D(QHttpSocketEngine); + if (d->state != Connected && d->socket->bytesAvailable() == 0) + return; + + if (d->state == Connected) { + // Forward as a read notification. + if (d->readNotificationEnabled) + emitReadNotification(); + return; + } + + readResponseContent: + if (d->state == ReadResponseContent) { + char dummybuffer[4096]; + while (d->pendingResponseData) { + int read = d->socket->read(dummybuffer, qMin(sizeof(dummybuffer), (size_t)d->pendingResponseData)); + if (read >= 0) + dummybuffer[read] = 0; + + if (read == 0) + return; + if (read == -1) { + d->socket->disconnectFromHost(); + emitWriteNotification(); + return; + } + d->pendingResponseData -= read; + } + if (d->pendingResponseData > 0) + return; + d->state = SendAuthentication; + slotSocketConnected(); + return; + } + + // Still in handshake mode. Wait until we've got a full response. + bool done = false; + do { + d->readBuffer += d->socket->readLine(); + } while (!(done = d->readBuffer.endsWith("\r\n\r\n")) && d->socket->canReadLine()); + + if (!done) { + // Wait for more. + return; + } + + if (!d->readBuffer.startsWith("HTTP/1.")) { + // protocol error, this isn't HTTP + d->readBuffer.clear(); + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Did not receive HTTP response from proxy")); + emitConnectionNotification(); + return; + } + + QHttpResponseHeader responseHeader(QString::fromLatin1(d->readBuffer)); + d->readBuffer.clear(); // we parsed the proxy protocol response. from now on direct socket reading will be done + + int statusCode = responseHeader.statusCode(); + if (statusCode == 200) { + d->state = Connected; + setLocalAddress(d->socket->localAddress()); + setLocalPort(d->socket->localPort()); + setState(QAbstractSocket::ConnectedState); + } else if (statusCode == 407) { + if (d->authenticator.isNull()) + d->authenticator.detach(); + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + + priv->parseHttpResponse(responseHeader, true); + + if (priv->phase == QAuthenticatorPrivate::Invalid) { + // problem parsing the reply + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Error parsing authentication request from proxy")); + emitConnectionNotification(); + return; + } + + bool willClose; + QString proxyConnectionHeader = responseHeader.value(QLatin1String("Proxy-Connection")); + proxyConnectionHeader = proxyConnectionHeader.toLower(); + if (proxyConnectionHeader == QLatin1String("close")) { + willClose = true; + } else if (proxyConnectionHeader == QLatin1String("keep-alive")) { + willClose = false; + } else { + // no Proxy-Connection header, so use the default + // HTTP 1.1's default behaviour is to keep persistent connections + // HTTP 1.0 or earlier, so we expect the server to close + willClose = (responseHeader.majorVersion() * 0x100 + responseHeader.minorVersion()) <= 0x0100; + } + + if (willClose) { + // the server will disconnect, so let's avoid receiving an error + // especially since the signal below may trigger a new event loop + d->socket->disconnectFromHost(); + d->socket->readAll(); + } + + if (priv->phase == QAuthenticatorPrivate::Done) + emit proxyAuthenticationRequired(d->proxy, &d->authenticator); + + // priv->phase will get reset to QAuthenticatorPrivate::Start if the authenticator got modified in the signal above. + if (priv->phase == QAuthenticatorPrivate::Done) { + setError(QAbstractSocket::ProxyAuthenticationRequiredError, tr("Authentication required")); + d->socket->disconnectFromHost(); + } else { + // close the connection if it isn't already and reconnect using the chosen authentication method + d->state = SendAuthentication; + if (willClose) { + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } else { + bool ok; + int contentLength = responseHeader.value(QLatin1String("Content-Length")).toInt(&ok); + if (ok && contentLength > 0) { + d->state = ReadResponseContent; + d->pendingResponseData = contentLength; + goto readResponseContent; + } else { + d->state = SendAuthentication; + slotSocketConnected(); + } + } + return; + } + } else { + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + if (statusCode == 403 || statusCode == 405) { + // 403 Forbidden + // 405 Method Not Allowed + setError(QAbstractSocket::SocketAccessError, tr("Proxy denied connection")); + } else if (statusCode == 404) { + // 404 Not Found: host lookup error + setError(QAbstractSocket::HostNotFoundError, QAbstractSocket::tr("Host not found")); + } else if (statusCode == 503) { + // 503 Service Unavailable: Connection Refused + setError(QAbstractSocket::ConnectionRefusedError, QAbstractSocket::tr("Connection refused")); + } else { + // Some other reply + //qWarning("UNEXPECTED RESPONSE: [%s]", responseHeader.toString().toLatin1().data()); + setError(QAbstractSocket::ProxyProtocolError, tr("Error communicating with HTTP proxy")); + } + } + + // The handshake is done; notify that we're connected (or failed to connect) + emitConnectionNotification(); +} + +void QHttpSocketEngine::slotSocketBytesWritten() +{ + Q_D(QHttpSocketEngine); + if (d->state == Connected && d->writeNotificationEnabled) + emitWriteNotification(); +} + +void QHttpSocketEngine::slotSocketError(QAbstractSocket::SocketError error) +{ + Q_D(QHttpSocketEngine); + d->readBuffer.clear(); + + if (d->state != Connected) { + // we are in proxy handshaking stages + if (error == QAbstractSocket::HostNotFoundError) + setError(QAbstractSocket::ProxyNotFoundError, tr("Proxy server not found")); + else if (error == QAbstractSocket::ConnectionRefusedError) + setError(QAbstractSocket::ProxyConnectionRefusedError, tr("Proxy connection refused")); + else if (error == QAbstractSocket::SocketTimeoutError) + setError(QAbstractSocket::ProxyConnectionTimeoutError, tr("Proxy server connection timed out")); + else if (error == QAbstractSocket::RemoteHostClosedError) + setError(QAbstractSocket::ProxyConnectionClosedError, tr("Proxy connection closed prematurely")); + else + setError(error, d->socket->errorString()); + emitConnectionNotification(); + return; + } + + // We're connected + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error + + d->state = None; + setError(error, d->socket->errorString()); + if (error != QAbstractSocket::RemoteHostClosedError) + qDebug() << "QHttpSocketEngine::slotSocketError: got weird error =" << error; + //read notification needs to always be emitted, otherwise the higher layer doesn't get the disconnected signal + emitReadNotification(); +} + +void QHttpSocketEngine::slotSocketStateChanged(QAbstractSocket::SocketState state) +{ + Q_UNUSED(state); +} + +void QHttpSocketEngine::emitPendingReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationPending = false; + if (d->readNotificationEnabled) + emit readNotification(); +} + +void QHttpSocketEngine::emitPendingWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationPending = false; + if (d->writeNotificationEnabled) + emit writeNotification(); +} + +void QHttpSocketEngine::emitPendingConnectionNotification() +{ + Q_D(QHttpSocketEngine); + d->connectionNotificationPending = false; + emit connectionNotification(); +} + +void QHttpSocketEngine::emitReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationActivated = true; + // if there is a connection notification pending we have to emit the readNotification + // incase there is connection error. This is only needed for Windows, but it does not + // hurt in other cases. + if ((d->readNotificationEnabled && !d->readNotificationPending) || d->connectionNotificationPending) { + d->readNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationActivated = true; + if (d->writeNotificationEnabled && !d->writeNotificationPending) { + d->writeNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitConnectionNotification() +{ + Q_D(QHttpSocketEngine); + if (!d->connectionNotificationPending) { + d->connectionNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingConnectionNotification", Qt::QueuedConnection); + } +} + +QHttpSocketEnginePrivate::QHttpSocketEnginePrivate() + : readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) + , pendingResponseData(0) +{ + socket = 0; + state = QHttpSocketEngine::None; +} + +QHttpSocketEnginePrivate::~QHttpSocketEnginePrivate() +{ +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, + QObject *parent) +{ + if (socketType != QAbstractSocket::TcpSocket) + return 0; + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::HttpProxy) + return 0; + + // we only accept active sockets + if (!qobject_cast<QAbstractSocket *>(parent)) + return 0; + + QHttpSocketEngine *engine = new QHttpSocketEngine(parent); + engine->setProxy(proxy); + return engine; +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(int, QObject *) +{ + return 0; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h new file mode 100644 index 0000000000..361ef5c693 --- /dev/null +++ b/src/network/socket/qhttpsocketengine_p.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** 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 QHTTPSOCKETENGINE_P_H +#define QHTTPSOCKETENGINE_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/qabstractsocketengine_p.h" +#include "qabstractsocket.h" +#include "qnetworkproxy.h" +#include "private/qauthenticator_p.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) + +class QTcpSocket; +class QHttpSocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QHttpSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + enum HttpState { + None, + ConnectSent, + Connected, + SendAuthentication, + ReadResponseContent + }; + QHttpSocketEngine(QObject *parent = 0); + ~QHttpSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif // QT_NO_NETWORKINTERFACE + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + qint64 bytesToWrite() const; + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public slots: + void slotSocketConnected(); + void slotSocketDisconnected(); + void slotSocketReadNotification(); + void slotSocketBytesWritten(); + void slotSocketError(QAbstractSocket::SocketError error); + void slotSocketStateChanged(QAbstractSocket::SocketState state); + +private slots: + void emitPendingReadNotification(); + void emitPendingWriteNotification(); + void emitPendingConnectionNotification(); + +private: + void emitReadNotification(); + void emitWriteNotification(); + void emitConnectionNotification(); + + Q_DECLARE_PRIVATE(QHttpSocketEngine) + Q_DISABLE_COPY(QHttpSocketEngine) + +}; + + +class QHttpSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QHttpSocketEngine) +public: + QHttpSocketEnginePrivate(); + ~QHttpSocketEnginePrivate(); + + QNetworkProxy proxy; + QString peerName; + QTcpSocket *socket; + QByteArray readBuffer; // only used for parsing the proxy response + QHttpSocketEngine::HttpState state; + QAuthenticator authenticator; + bool readNotificationEnabled; + bool writeNotificationEnabled; + bool exceptNotificationEnabled; + bool readNotificationActivated; + bool writeNotificationActivated; + bool readNotificationPending; + bool writeNotificationPending; + bool connectionNotificationPending; + uint pendingResponseData; +}; + +class Q_AUTOTEST_EXPORT QHttpSocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; +#endif + +QT_END_NAMESPACE + +#endif // QHTTPSOCKETENGINE_H diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp new file mode 100644 index 0000000000..46822d7bad --- /dev/null +++ b/src/network/socket/qlocalserver.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** 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 "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LOCALSERVER + +/*! + \class QLocalServer + \since 4.4 + + \brief The QLocalServer class provides a local socket based server. + + This class makes it possible to accept incoming local socket + connections. + + Call listen() to have the server start listening + for incoming connections on a specified key. The + newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection + as a connected QLocalSocket. The function returns a pointer to a + QLocalSocket that can be used for communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + When listening for connections, the name which the server is + listening on is available through serverName(). + + Calling close() makes QLocalServer stop listening for incoming connections. + + Although QLocalServer is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForNewConnection(), + which blocks until either a connection is available or a timeout expires. + + \sa QLocalSocket, QTcpServer +*/ + +/*! + Create a new local socket server with the given \a parent. + + \sa listen() + */ +QLocalServer::QLocalServer(QObject *parent) + : QObject(*new QLocalServerPrivate, parent) +{ + Q_D(QLocalServer); + d->init(); +} + +/*! + Destroys the QLocalServer object. If the server is listening for + connections, it is automatically closed. + + Any client QLocalSockets that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() + */ +QLocalServer::~QLocalServer() +{ + if (isListening()) + close(); +} + +/*! + Stop listening for incoming connections. Existing connections are not + effected, but any new connections will be refused. + + \sa isListening(), listen() + */ +void QLocalServer::close() +{ + Q_D(QLocalServer); + if (!isListening()) + return; + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + d->closeServer(); + d->serverName.clear(); + d->fullServerName.clear(); + d->errorString.clear(); + d->error = QAbstractSocket::UnknownSocketError; +} + +/*! + Returns the human-readable message appropriate to the current error + reported by serverError(). If no suitable string is available, an empty + string is returned. + + \sa serverError() + */ +QString QLocalServer::errorString() const +{ + Q_D(const QLocalServer); + return d->errorString; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() + */ +bool QLocalServer::hasPendingConnections() const +{ + Q_D(const QLocalServer); + return !(d->pendingConnections.isEmpty()); +} + +/*! + This virtual function is called by QLocalServer when a new connection + is available. \a socketDescriptor is the native socket descriptor for + the accepted connection. + + The base implementation creates a QLocalSocket, sets the socket descriptor + and then stores the QLocalSocket in an internal list of pending + connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior + when a connection is available. + + \sa newConnection(), nextPendingConnection(), + QLocalSocket::setSocketDescriptor() + */ +void QLocalServer::incomingConnection(quintptr socketDescriptor) +{ + Q_D(QLocalServer); + QLocalSocket *socket = new QLocalSocket(this); + socket->setSocketDescriptor(socketDescriptor); + d->pendingConnections.enqueue(socket); + emit newConnection(); +} + +/*! + Returns true if the server is listening for incoming connections + otherwise false. + + \sa listen(), close() + */ +bool QLocalServer::isListening() const +{ + Q_D(const QLocalServer); + return !(d->serverName.isEmpty()); +} + +/*! + Tells the server to listen for incoming connections on \a name. + If the server is currently listening then it will return false. + Return true on success otherwise false. + + \a name can be a single name and QLocalServer will determine + the correct platform specific path. serverName() will return + the name that is passed into listen. + + Usually you would just pass in a name like "foo", but on Unix this + could also be a path such as "/tmp/foo" and on Windows this could + be a pipe path such as "\\\\.\\pipe\\foo" + + Note: + On Unix if the server crashes without closing listen will fail + with AddressInUseError. To create a new server the file should be removed. + On Windows two local servers can listen to the same pipe at the same + time, but any connections will go to one of the server. + + \sa serverName(), isListening(), close() + */ +bool QLocalServer::listen(const QString &name) +{ + Q_D(QLocalServer); + if (isListening()) { + qWarning("QLocalServer::listen() called when already listening"); + return false; + } + + if (name.isEmpty()) { + d->error = QAbstractSocket::HostNotFoundError; + QString function = QLatin1String("QLocalServer::listen"); + d->errorString = tr("%1: Name error").arg(function); + return false; + } + + if (!d->listen(name)) { + d->serverName.clear(); + d->fullServerName.clear(); + return false; + } + + d->serverName = name; + return true; +} + +/*! + Returns the maximum number of pending accepted connections. + The default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() + */ +int QLocalServer::maxPendingConnections() const +{ + Q_D(const QLocalServer); + return d->maxPendingConnections; +} + +/*! + \fn void QLocalServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +/*! + Returns the next pending connection as a connected QLocalSocket object. + + The socket is created as a child of the server, which means that it is + automatically deleted when the QLocalServer object is destroyed. It is + still a good idea to delete the object explicitly when you are done with + it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \sa hasPendingConnections(), newConnection(), incomingConnection() + */ +QLocalSocket *QLocalServer::nextPendingConnection() +{ + Q_D(QLocalServer); + if (d->pendingConnections.isEmpty()) + return 0; + QLocalSocket *nextSocket = d->pendingConnections.dequeue(); +#ifndef QT_LOCALSOCKET_TCP +#ifdef Q_OS_SYMBIAN + if(!d->socketNotifier) + return nextSocket; +#endif + if (d->pendingConnections.size() <= d->maxPendingConnections) +#ifndef Q_OS_WIN + d->socketNotifier->setEnabled(true); +#else + d->connectionEventNotifier->setEnabled(true); +#endif +#endif + return nextSocket; +} + +/*! + \since 4.5 + + Removes any server instance that might cause a call to listen() to fail + and returns true if successful; otherwise returns false. + This function is meant to recover from a crash, when the previous server + instance has not been cleaned up. + + On Windows, this function does nothing; on Unix, it removes the socket file + given by \a name. + + \warning Be careful to avoid removing sockets of running instances. +*/ +bool QLocalServer::removeServer(const QString &name) +{ + return QLocalServerPrivate::removeServer(name); +} + +/*! + Returns the server name if the server is listening for connections; + otherwise returns QString() + + \sa listen(), fullServerName() + */ +QString QLocalServer::serverName() const +{ + Q_D(const QLocalServer); + return d->serverName; +} + +/*! + Returns the full path that the server is listening on. + + Note: This is platform specific + + \sa listen(), serverName() + */ +QString QLocalServer::fullServerName() const +{ + Q_D(const QLocalServer); + return d->fullServerName; +} + +/*! + Returns the type of error that occurred last or NoError. + + \sa errorString() + */ +QAbstractSocket::SocketError QLocalServer::serverError() const +{ + Q_D(const QLocalServer); + return d->error; +} + +/*! + Sets the maximum number of pending accepted connections to + \a numConnections. QLocalServer will accept no more than + \a numConnections incoming connections before nextPendingConnection() + is called. + + Note: Even though QLocalServer will stop accepting new connections + after it has reached its maximum number of pending connections, + the operating system may still keep them in queue which will result + in clients signaling that it is connected. + + \sa maxPendingConnections(), hasPendingConnections() + */ +void QLocalServer::setMaxPendingConnections(int numConnections) +{ + Q_D(QLocalServer); + d->maxPendingConnections = numConnections; +} + +/*! + Waits for at most \a msec milliseconds or until an incoming connection + is available. Returns true if a connection is available; otherwise + returns false. If the operation timed out and \a timedOut is not 0, + *timedOut will be set to true. + + This is a blocking function call. Its use is ill-advised in a + single-threaded GUI application, since the whole application will stop + responding until the function returns. waitForNewConnection() is mostly + useful when there is no event loop available. + + The non-blocking alternative is to connect to the newConnection() signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() + */ +bool QLocalServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QLocalServer); + if (timedOut) + *timedOut = false; + + if (!isListening()) + return false; + + d->waitForNewConnection(msec, timedOut); + + return !d->pendingConnections.isEmpty(); +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qlocalserver.cpp" + diff --git a/src/network/socket/qlocalserver.h b/src/network/socket/qlocalserver.h new file mode 100644 index 0000000000..e8dc1c9b70 --- /dev/null +++ b/src/network/socket/qlocalserver.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 QLOCALSERVER_H +#define QLOCALSERVER_H + +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSERVER + +class QLocalSocket; +class QLocalServerPrivate; + +class Q_NETWORK_EXPORT QLocalServer : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalServer) + +Q_SIGNALS: + void newConnection(); + +public: + QLocalServer(QObject *parent = 0); + ~QLocalServer(); + + void close(); + QString errorString() const; + virtual bool hasPendingConnections() const; + bool isListening() const; + bool listen(const QString &name); + int maxPendingConnections() const; + virtual QLocalSocket *nextPendingConnection(); + QString serverName() const; + QString fullServerName() const; + static bool removeServer(const QString &name); + QAbstractSocket::SocketError serverError() const; + void setMaxPendingConnections(int numConnections); + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + +protected: + virtual void incomingConnection(quintptr socketDescriptor); + +private: + Q_DISABLE_COPY(QLocalServer) + Q_PRIVATE_SLOT(d_func(), void _q_onNewConnection()) +}; + +#endif // QT_NO_LOCALSERVER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSERVER_H + diff --git a/src/network/socket/qlocalserver_p.h b/src/network/socket/qlocalserver_p.h new file mode 100644 index 0000000000..1ee5df2558 --- /dev/null +++ b/src/network/socket/qlocalserver_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 QLOCALSERVER_P_H +#define QLOCALSERVER_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 QLocalServer class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSERVER + +#include "qlocalserver.h" +#include "private/qobject_p.h" +#include <qqueue.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include <qtcpserver.h> +#elif defined(Q_OS_WIN) +# include <qt_windows.h> +# include <private/qwineventnotifier_p.h> +#else +# include <private/qabstractsocketengine_p.h> +# include <qsocketnotifier.h> +#endif + +QT_BEGIN_NAMESPACE + +class QLocalServerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QLocalServer) + +public: + QLocalServerPrivate() : +#if !defined(QT_LOCALSOCKET_TCP) && !defined(Q_OS_WIN) + listenSocket(-1), socketNotifier(0), +#endif + maxPendingConnections(30), error(QAbstractSocket::UnknownSocketError) + { + } + + void init(); + bool listen(const QString &name); + static bool removeServer(const QString &name); + void closeServer(); + void waitForNewConnection(int msec, bool *timedOut); + void _q_onNewConnection(); + +#if defined(QT_LOCALSOCKET_TCP) + + QTcpServer tcpServer; + QMap<quintptr, QTcpSocket*> socketMap; +#elif defined(Q_OS_WIN) + struct Listener { + HANDLE handle; + OVERLAPPED overlapped; + bool connected; + }; + + void setError(const QString &function); + bool addListener(); + + QList<Listener> listeners; + HANDLE eventHandle; + QWinEventNotifier *connectionEventNotifier; +#else + void setError(const QString &function); + + int listenSocket; + QSocketNotifier *socketNotifier; +#endif + + QString serverName; + QString fullServerName; + int maxPendingConnections; + QQueue<QLocalSocket*> pendingConnections; + QString errorString; + QAbstractSocket::SocketError error; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER + +#endif // QLOCALSERVER_P_H + diff --git a/src/network/socket/qlocalserver_tcp.cpp b/src/network/socket/qlocalserver_tcp.cpp new file mode 100644 index 0000000000..aeda8635af --- /dev/null +++ b/src/network/socket/qlocalserver_tcp.cpp @@ -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$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ + Q_Q(QLocalServer); + q->connect(&tcpServer, SIGNAL(newConnection()), SLOT(_q_onNewConnection())); +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + if (!tcpServer.listen(QHostAddress::LocalHost)) + return false; + + const QLatin1String prefix("QLocalServer/"); + if (requestedServerName.startsWith(prefix)) + fullServerName = requestedServerName; + else + fullServerName = prefix + requestedServerName; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(fullServerName)) { + qWarning("QLocalServer::listen: server name is already in use."); + tcpServer.close(); + return false; + } + + settings.setValue(fullServerName, tcpServer.serverPort()); + return true; +} + +void QLocalServerPrivate::closeServer() +{ + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (fullServerName == QLatin1String("QLocalServer")) + settings.setValue(fullServerName, QVariant()); + else + settings.remove(fullServerName); + tcpServer.close(); +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + if (pendingConnections.isEmpty()) + tcpServer.waitForNewConnection(msec, timedOut); + else if (timedOut) + *timedOut = false; +} + +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + QTcpSocket* tcpSocket = tcpServer.nextPendingConnection(); + if (!tcpSocket) { + qWarning("QLocalServer: no pending connection"); + return; + } + + tcpSocket->setParent(q); + const quintptr socketDescriptor = tcpSocket->socketDescriptor(); + q->incomingConnection(socketDescriptor); +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + const QLatin1String prefix("QLocalServer/"); + QString serverName; + if (name.startsWith(prefix)) + serverName = name; + else + serverName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(serverName)) + settings.remove(serverName); + + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalserver_unix.cpp b/src/network/socket/qlocalserver_unix.cpp new file mode 100644 index 0000000000..bc07fcf235 --- /dev/null +++ b/src/network/socket/qlocalserver_unix.cpp @@ -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$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qnet_unix_p.h" + +#ifndef QT_NO_LOCALSERVER + +#include <sys/socket.h> +#include <sys/un.h> + +#include <qdebug.h> +#include <qdir.h> +#include <qdatetime.h> + +#ifdef Q_OS_VXWORKS +# include <selectLib.h> +#endif + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + QString fileName; + if (name.startsWith(QLatin1Char('/'))) { + fileName = name; + } else { + fileName = QDir::cleanPath(QDir::tempPath()); + fileName += QLatin1Char('/') + name; + } + if (QFile::exists(fileName)) + return QFile::remove(fileName); + else + return true; +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + Q_Q(QLocalServer); + + // determine the full server path + if (requestedServerName.startsWith(QLatin1Char('/'))) { + fullServerName = requestedServerName; + } else { + fullServerName = QDir::cleanPath(QDir::tempPath()); + fullServerName += QLatin1Char('/') + requestedServerName; + } + serverName = requestedServerName; + + // create the unix socket + listenSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0); + if (-1 == listenSocket) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + + // Construct the unix address + struct ::sockaddr_un addr; + addr.sun_family = PF_UNIX; + if (sizeof(addr.sun_path) < (uint)fullServerName.toLatin1().size() + 1) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + ::memcpy(addr.sun_path, fullServerName.toLatin1().data(), + fullServerName.toLatin1().size() + 1); + +#ifdef Q_OS_SYMBIAN + // In SYMBIAN OS it can currently happen that accept is called twice, + // once from waitForNewConnection and once via QSocketNotfier activity + // + // As an workaround, we set the socket to non blocking so possible + // subsequent call to accept will not block in any case + // + // This change can be removed once more generic fix to select thread + // synchronization problem is implemented. + int flags = fcntl(listenSocket, F_GETFL, 0); + if (-1 == flags + || -1 == (fcntl(listenSocket, F_SETFL, flags | O_NONBLOCK))) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } +#endif + + // bind + if(-1 == QT_SOCKET_BIND(listenSocket, (sockaddr *)&addr, sizeof(sockaddr_un))) { + setError(QLatin1String("QLocalServer::listen")); + // if address is in use already, just close the socket, but do not delete the file + if(errno == EADDRINUSE) + QT_CLOSE(listenSocket); + // otherwise, close the socket and delete the file + else + closeServer(); + listenSocket = -1; + return false; + } + + // listen for connections + if (-1 == qt_safe_listen(listenSocket, 50)) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + listenSocket = -1; + if (error != QAbstractSocket::AddressInUseError) + QFile::remove(fullServerName); + return false; + } + Q_ASSERT(!socketNotifier); + socketNotifier = new QSocketNotifier(listenSocket, + QSocketNotifier::Read, q); + q->connect(socketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_onNewConnection())); + socketNotifier->setEnabled(maxPendingConnections > 0); + return true; +} + +/*! + \internal + + \sa QLocalServer::closeServer() + */ +void QLocalServerPrivate::closeServer() +{ + if (-1 != listenSocket) + QT_CLOSE(listenSocket); + listenSocket = -1; + + if (socketNotifier) { + socketNotifier->setEnabled(false); // Otherwise, closed socket is checked before deleter runs + socketNotifier->deleteLater(); + socketNotifier = 0; + } + + if (!fullServerName.isEmpty()) + QFile::remove(fullServerName); +} + +/*! + \internal + + We have received a notification that we can read on the listen socket. + Accept the new socket. + */ +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + if (-1 == listenSocket) + return; + + ::sockaddr_un addr; + QT_SOCKLEN_T length = sizeof(sockaddr_un); + int connectedSocket = qt_safe_accept(listenSocket, (sockaddr *)&addr, &length); + if(-1 == connectedSocket) { + setError(QLatin1String("QLocalSocket::activated")); + closeServer(); + } else { + socketNotifier->setEnabled(pendingConnections.size() + <= maxPendingConnections); + q->incomingConnection(connectedSocket); + } +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket, &readfds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + int result = -1; + result = qt_safe_select(listenSocket + 1, &readfds, 0, 0, (msec == -1) ? 0 : &timeout); + if (-1 == result) { + setError(QLatin1String("QLocalServer::waitForNewConnection")); + closeServer(); + } + if (result > 0) + _q_onNewConnection(); + if (timedOut) + *timedOut = (result == 0); +} + +void QLocalServerPrivate::setError(const QString &function) +{ + if (EAGAIN == errno) + return; + + switch (errno) { + case EACCES: + errorString = QLocalServer::tr("%1: Permission denied").arg(function); + error = QAbstractSocket::SocketAccessError; + break; + case ELOOP: + case ENOENT: + case ENAMETOOLONG: + case EROFS: + case ENOTDIR: + errorString = QLocalServer::tr("%1: Name error").arg(function); + error = QAbstractSocket::HostNotFoundError; + break; + case EADDRINUSE: + errorString = QLocalServer::tr("%1: Address in use").arg(function); + error = QAbstractSocket::AddressInUseError; + break; + + default: + errorString = QLocalServer::tr("%1: Unknown error %2") + .arg(function).arg(errno); + error = QAbstractSocket::UnknownSocketError; +#if defined QLOCALSERVER_DEBUG + qWarning() << errorString << "fullServerName:" << fullServerName; +#endif + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER diff --git a/src/network/socket/qlocalserver_win.cpp b/src/network/socket/qlocalserver_win.cpp new file mode 100644 index 0000000000..fb1015792f --- /dev/null +++ b/src/network/socket/qlocalserver_win.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** 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 "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +#include <qdebug.h> + +// The buffer size need to be 0 otherwise data could be +// lost if the socket that has written data closes the connection +// before it is read. Pipewriter is used for write buffering. +#define BUFSIZE 0 + +// ###: This should be a property. Should replace the insane 50 on unix as well. +#define SYSTEM_MAX_PENDING_SOCKETS 8 + +QT_BEGIN_NAMESPACE + +bool QLocalServerPrivate::addListener() +{ + // The object must not change its address once the + // contained OVERLAPPED struct is passed to Windows. + listeners << Listener(); + Listener &listener = listeners.last(); + + listener.handle = CreateNamedPipe( + (const wchar_t *)fullServerName.utf16(), // pipe name + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access + PIPE_TYPE_BYTE | // byte type pipe + PIPE_READMODE_BYTE | // byte-read mode + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + BUFSIZE, // output buffer size + BUFSIZE, // input buffer size + 3000, // client time-out + NULL); + + if (listener.handle == INVALID_HANDLE_VALUE) { + setError(QLatin1String("QLocalServerPrivate::addListener")); + listeners.removeLast(); + return false; + } + + memset(&listener.overlapped, 0, sizeof(listener.overlapped)); + listener.overlapped.hEvent = eventHandle; + if (!ConnectNamedPipe(listener.handle, &listener.overlapped)) { + switch (GetLastError()) { + case ERROR_IO_PENDING: + listener.connected = false; + break; + case ERROR_PIPE_CONNECTED: + listener.connected = true; + SetEvent(eventHandle); + break; + default: + CloseHandle(listener.handle); + setError(QLatin1String("QLocalServerPrivate::addListener")); + listeners.removeLast(); + return false; + } + } else { + Q_ASSERT_X(false, "QLocalServerPrivate::addListener", "The impossible happened"); + SetEvent(eventHandle); + } + return true; +} + +void QLocalServerPrivate::setError(const QString &function) +{ + int windowsError = GetLastError(); + errorString = QString::fromLatin1("%1: %2").arg(function).arg(qt_error_string(windowsError)); + error = QAbstractSocket::UnknownSocketError; +} + +void QLocalServerPrivate::init() +{ +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + Q_UNUSED(name); + return true; +} + +bool QLocalServerPrivate::listen(const QString &name) +{ + Q_Q(QLocalServer); + + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + fullServerName = name; + else + fullServerName = pipePath + name; + + // Use only one event for all listeners of one socket. + // The idea is that listener events are rare, so polling all listeners once in a while is + // cheap compared to waiting for N additional events in each iteration of the main loop. + eventHandle = CreateEvent(NULL, TRUE, FALSE, NULL); + connectionEventNotifier = new QWinEventNotifier(eventHandle , q); + q->connect(connectionEventNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_onNewConnection())); + + for (int i = 0; i < SYSTEM_MAX_PENDING_SOCKETS; ++i) + if (!addListener()) + return false; + return true; +} + +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + DWORD dummy; + + // Reset first, otherwise we could reset an event which was asserted + // immediately after we checked the conn status. + ResetEvent(eventHandle); + + // Testing shows that there is indeed absolutely no guarantee which listener gets + // a client connection first, so there is no way around polling all of them. + for (int i = 0; i < listeners.size(); ) { + HANDLE handle = listeners[i].handle; + if (listeners[i].connected + || GetOverlappedResult(handle, &listeners[i].overlapped, &dummy, FALSE)) + { + listeners.removeAt(i); + + addListener(); + + if (pendingConnections.size() > maxPendingConnections) + connectionEventNotifier->setEnabled(false); + + // Make this the last thing so connected slots can wreak the least havoc + q->incomingConnection((quintptr)handle); + } else { + if (GetLastError() != ERROR_IO_INCOMPLETE) { + q->close(); + setError(QLatin1String("QLocalServerPrivate::_q_onNewConnection")); + return; + } + + ++i; + } + } +} + +void QLocalServerPrivate::closeServer() +{ + connectionEventNotifier->setEnabled(false); // Otherwise, closed handle is checked before deleter runs + connectionEventNotifier->deleteLater(); + connectionEventNotifier = 0; + CloseHandle(eventHandle); + for (int i = 0; i < listeners.size(); ++i) + CloseHandle(listeners[i].handle); + listeners.clear(); +} + +void QLocalServerPrivate::waitForNewConnection(int msecs, bool *timedOut) +{ + Q_Q(QLocalServer); + if (!pendingConnections.isEmpty() || !q->isListening()) + return; + + DWORD result = WaitForSingleObject(eventHandle, (msecs == -1) ? INFINITE : msecs); + if (result == WAIT_TIMEOUT) { + if (timedOut) + *timedOut = true; + } else { + _q_onNewConnection(); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket.cpp b/src/network/socket/qlocalsocket.cpp new file mode 100644 index 0000000000..9a2b0ba3cd --- /dev/null +++ b/src/network/socket/qlocalsocket.cpp @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** 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 "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#ifndef QT_NO_LOCALSOCKET + +QT_BEGIN_NAMESPACE + +/*! + \class QLocalSocket + \since 4.4 + + \brief The QLocalSocket class provides a local socket. + + On Windows this is a named pipe and on Unix this is a local domain socket. + + If an error occurs, socketError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + Although QLocalSocket is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForConnected(), + waitForReadyRead(), waitForBytesWritten(), and waitForDisconnected() + which blocks until the operation is complete or the timeout expires. + + Note that this feature is not supported on versions of Windows earlier than + Windows XP. + + \sa QLocalServer +*/ + +/*! + \fn void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) + + Attempts to make a connection to \a name. + + The socket is opened in the given \a openMode and first enters ConnectingState. + It then attempts to connect to the address or addresses returned by the lookup. + Finally, if a connection is established, QLocalSocket enters ConnectedState + and emits connected(). + + At any point, the socket can emit error() to signal that an error occurred. + + See also state(), serverName(), and waitForConnected(). +*/ + +/*! + \fn void QLocalSocket::connected() + + This signal is emitted after connectToServer() has been called and + a connection has been successfully established. + + \sa connectToServer(), disconnected() +*/ + +/*! + \fn bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) + + Initializes QLocalSocket with the native socket descriptor + \a socketDescriptor. Returns true if socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. The socket is + opened in the mode specified by \a openMode, and enters the socket state + specified by \a socketState. + + \note It is not possible to initialize two local sockets with the same + native socket descriptor. + + \sa socketDescriptor(), state(), openMode() +*/ + +/*! + \fn quintptr QLocalSocket::socketDescriptor() const + + Returns the native socket descriptor of the QLocalSocket object if + this is available; otherwise returns -1. + + The socket descriptor is not available when QLocalSocket + is in UnconnectedState. + + \sa setSocketDescriptor() +*/ + +/*! + \fn qint64 QLocalSocket::readData(char *data, qint64 c) + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::writeData(const char *data, qint64 c) + \reimp +*/ + +/*! + \fn void QLocalSocket::abort() + + Aborts the current connection and resets the socket. + Unlike disconnectFromServer(), this function immediately closes the socket, + clearing any pending data in the write buffer. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn qint64 QLocalSocket::bytesAvailable() const + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::bytesToWrite() const + \reimp +*/ + +/*! + \fn bool QLocalSocket::canReadLine() const + \reimp +*/ + +/*! + \fn void QLocalSocket::close() + \reimp +*/ + +/*! + \fn bool QLocalSocket::waitForBytesWritten(int msecs) + \reimp +*/ + +/*! + \fn bool QLocalSocket::flush() + + This function writes as much as possible from the internal write buffer + to the socket, without blocking. If any data was written, this function + returns true; otherwise false is returned. + + Call this function if you need QLocalSocket to start sending buffered data + immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QLocalSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnectFromServer() + + Attempts to close the socket. If there is pending data waiting to be + written, QLocalSocket will enter ClosingState and wait until all data + has been written. Eventually, it will enter UnconnectedState and emit + the disconnectedFromServer() signal. + + \sa connectToServer() +*/ + +/*! + \fn QLocalSocket::LocalSocketError QLocalSocket::error() const + + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ + +/*! + \fn bool QLocalSocket::isValid() const + + Returns true if the socket is valid and ready for use; otherwise + returns false. + + \note The socket's state must be ConnectedState before reading + and writing can occur. + + \sa state(), connectToServer() +*/ + +/*! + \fn qint64 QLocalSocket::readBufferSize() const + + Returns the size of the internal read buffer. This limits the amount of + data that the client can receive before you call read() or readAll(). + A read buffer size of 0 (the default) means that the buffer has no size + limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ + +/*! + \fn void QLocalSocket::setReadBufferSize(qint64 size) + + Sets the size of QLocalSocket's internal read buffer to be \a size bytes. + + If the buffer size is limited to a certain size, QLocalSocket won't + buffer more than this size of data. Exceptionally, a buffer size of 0 + means that the read buffer is unlimited and all incoming data is buffered. + This is the default. + + This option is useful if you only read the data at certain points in + time (e.g., in a real-time streaming application) or if you want to + protect your socket against receiving too much data, which may eventually + cause your application to run out of memory. + + \sa readBufferSize(), read() +*/ + +/*! + \fn bool QLocalSocket::waitForConnected(int msecs) + + Waits until the socket is connected, up to \a msecs milliseconds. If the + connection has been established, this function returns true; otherwise + it returns false. In the case where it returns false, you can call + error() to determine the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 0 + + If \a msecs is -1, this function will not time out. + + \sa connectToServer(), connected() +*/ + +/*! + \fn bool QLocalSocket::waitForDisconnected(int msecs) + + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 1 + + If \a msecs is -1, this function will not time out. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn bool QLocalSocket::waitForReadyRead(int msecs) + + This function blocks until data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if data is available for reading; + otherwise it returns false (if an error occurred or the + operation timed out). + + \sa waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \sa connectToServer(), disconnectFromServer(), abort(), connected() +*/ + +/*! + \fn void QLocalSocket::error(QLocalSocket::LocalSocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QLocalSocket::LocalSocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa error(), errorString(), {Creating Custom Qt Types} +*/ + +/*! + \fn void QLocalSocket::stateChanged(QLocalSocket::LocalSocketState socketState) + + This signal is emitted whenever QLocalSocket's state changes. + The \a socketState parameter is the new state. + + QLocalSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE() and + qRegisterMetaType(). + + \sa state(), {Creating Custom Qt Types} +*/ + +/*! + Creates a new local socket. The \a parent argument is passed to + QObject's constructor. + */ +QLocalSocket::QLocalSocket(QObject * parent) + : QIODevice(*new QLocalSocketPrivate, parent) +{ + Q_D(QLocalSocket); + d->init(); +} + +/*! + Destroys the socket, closing the connection if necessary. + */ +QLocalSocket::~QLocalSocket() +{ + close(); +#if !defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) + Q_D(QLocalSocket); + d->unixSocket.setParent(0); +#endif +} + +/*! + Returns the name of the peer as specified by connectToServer(), or an + empty QString if connectToServer() has not been called or it failed. + + \sa connectToServer(), fullServerName() + + */ +QString QLocalSocket::serverName() const +{ + Q_D(const QLocalSocket); + return d->serverName; +} + +/*! + Returns the server path that the socket is connected to. + + \note The return value of this function is platform specific. + + \sa connectToServer(), serverName() + */ +QString QLocalSocket::fullServerName() const +{ + Q_D(const QLocalSocket); + return d->fullServerName; +} + +/*! + Returns the state of the socket. + + \sa error() + */ +QLocalSocket::LocalSocketState QLocalSocket::state() const +{ + Q_D(const QLocalSocket); + return d->state; +} + +/*! \reimp +*/ +bool QLocalSocket::isSequential() const +{ + return true; +} + +/*! + \enum QLocalSocket::LocalSocketError + + The LocalServerError enumeration represents the errors that can occur. + The most recent error can be retrieved through a call to + \l QLocalSocket::error(). + + \value ConnectionRefusedError The connection was refused by + the peer (or timed out). + \value PeerClosedError The remote socket closed the connection. + Note that the client socket (i.e., this socket) will be closed + after the remote close notification has been sent. + \value ServerNotFoundError The local socket name was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the operating + system's limit (which can be as low as 8192 bytes). + \value ConnectionError An error occurred with the connection. + \value UnsupportedSocketOperationError The requested socket operation + is not supported by the local operating system. + \value UnknownSocketError An unidentified error occurred. + */ + +/*! + \enum QLocalSocket::LocalSocketState + + This enum describes the different states in which a socket can be. + + \sa QLocalSocket::state() + + \value UnconnectedState The socket is not connected. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value ClosingState The socket is about to close + (data may still be waiting to be written). + */ + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketError error) +{ + switch (error) { + case QLocalSocket::ConnectionRefusedError: + debug << "QLocalSocket::ConnectionRefusedError"; + break; + case QLocalSocket::PeerClosedError: + debug << "QLocalSocket::PeerClosedError"; + break; + case QLocalSocket::ServerNotFoundError: + debug << "QLocalSocket::ServerNotFoundError"; + break; + case QLocalSocket::SocketAccessError: + debug << "QLocalSocket::SocketAccessError"; + break; + case QLocalSocket::SocketResourceError: + debug << "QLocalSocket::SocketResourceError"; + break; + case QLocalSocket::SocketTimeoutError: + debug << "QLocalSocket::SocketTimeoutError"; + break; + case QLocalSocket::DatagramTooLargeError: + debug << "QLocalSocket::DatagramTooLargeError"; + break; + case QLocalSocket::ConnectionError: + debug << "QLocalSocket::ConnectionError"; + break; + case QLocalSocket::UnsupportedSocketOperationError: + debug << "QLocalSocket::UnsupportedSocketOperationError"; + break; + case QLocalSocket::UnknownSocketError: + debug << "QLocalSocket::UnknownSocketError"; + break; + default: + debug << "QLocalSocket::SocketError(" << int(error) << ')'; + break; + } + return debug; +} + +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketState state) +{ + switch (state) { + case QLocalSocket::UnconnectedState: + debug << "QLocalSocket::UnconnectedState"; + break; + case QLocalSocket::ConnectingState: + debug << "QLocalSocket::ConnectingState"; + break; + case QLocalSocket::ConnectedState: + debug << "QLocalSocket::ConnectedState"; + break; + case QLocalSocket::ClosingState: + debug << "QLocalSocket::ClosingState"; + break; + default: + debug << "QLocalSocket::SocketState(" << int(state) << ')'; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#endif + +#include "moc_qlocalsocket.cpp" diff --git a/src/network/socket/qlocalsocket.h b/src/network/socket/qlocalsocket.h new file mode 100644 index 0000000000..2f99b55a70 --- /dev/null +++ b/src/network/socket/qlocalsocket.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 QLOCALSOCKET_H +#define QLOCALSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSOCKET + +class QLocalSocketPrivate; + +class Q_NETWORK_EXPORT QLocalSocket : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalSocket) + +public: + enum LocalSocketError + { + ConnectionRefusedError = QAbstractSocket::ConnectionRefusedError, + PeerClosedError = QAbstractSocket::RemoteHostClosedError, + ServerNotFoundError = QAbstractSocket::HostNotFoundError, + SocketAccessError = QAbstractSocket::SocketAccessError, + SocketResourceError = QAbstractSocket::SocketResourceError, + SocketTimeoutError = QAbstractSocket::SocketTimeoutError, + DatagramTooLargeError = QAbstractSocket::DatagramTooLargeError, + ConnectionError = QAbstractSocket::NetworkError, + UnsupportedSocketOperationError = QAbstractSocket::UnsupportedSocketOperationError, + UnknownSocketError = QAbstractSocket::UnknownSocketError + }; + + enum LocalSocketState + { + UnconnectedState = QAbstractSocket::UnconnectedState, + ConnectingState = QAbstractSocket::ConnectingState, + ConnectedState = QAbstractSocket::ConnectedState, + ClosingState = QAbstractSocket::ClosingState + }; + + QLocalSocket(QObject *parent = 0); + ~QLocalSocket(); + + void connectToServer(const QString &name, OpenMode openMode = ReadWrite); + void disconnectFromServer(); + + QString serverName() const; + QString fullServerName() const; + + void abort(); + virtual bool isSequential() const; + virtual qint64 bytesAvailable() const; + virtual qint64 bytesToWrite() const; + virtual bool canReadLine() const; + virtual void close(); + LocalSocketError error() const; + bool flush(); + bool isValid() const; + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + bool setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState = ConnectedState, + OpenMode openMode = ReadWrite); + quintptr socketDescriptor() const; + + LocalSocketState state() const; + bool waitForBytesWritten(int msecs = 30000); + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + +Q_SIGNALS: + void connected(); + void disconnected(); + void error(QLocalSocket::LocalSocketError socketError); + void stateChanged(QLocalSocket::LocalSocketState socketState); + +protected: + virtual qint64 readData(char*, qint64); + virtual qint64 writeData(const char*, qint64); + +private: + Q_DISABLE_COPY(QLocalSocket) +#if defined(QT_LOCALSOCKET_TCP) + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) +#elif defined(Q_OS_WIN) + Q_PRIVATE_SLOT(d_func(), void _q_notified()) + Q_PRIVATE_SLOT(d_func(), void _q_canWrite()) + Q_PRIVATE_SLOT(d_func(), void _q_pipeClosed()) + Q_PRIVATE_SLOT(d_func(), void _q_emitReadyRead()) +#else + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) + Q_PRIVATE_SLOT(d_func(), void _q_connectToSocket()) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketState); +#endif + +#endif // QT_NO_LOCALSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSOCKET_H diff --git a/src/network/socket/qlocalsocket_p.h b/src/network/socket/qlocalsocket_p.h new file mode 100644 index 0000000000..09e50f512f --- /dev/null +++ b/src/network/socket/qlocalsocket_p.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 QLOCALSOCKET_P_H +#define QLOCALSOCKET_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 QLocalSocket class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSOCKET + +#include "qlocalsocket.h" +#include "private/qiodevice_p.h" + +#include <qtimer.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include "qtcpsocket.h" +#elif defined(Q_OS_WIN) +# include "private/qwindowspipewriter_p.h" +# include "private/qringbuffer_p.h" +# include <private/qwineventnotifier_p.h> +#else +# include "private/qabstractsocketengine_p.h" +# include <qtcpsocket.h> +# include <qsocketnotifier.h> +# include <errno.h> +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) +class QLocalUnixSocket : public QTcpSocket +{ + +public: + QLocalUnixSocket() : QTcpSocket() + { + }; + + inline void setSocketState(QAbstractSocket::SocketState state) + { + QTcpSocket::setSocketState(state); + }; + + inline void setErrorString(const QString &string) + { + QTcpSocket::setErrorString(string); + } + + inline void setSocketError(QAbstractSocket::SocketError error) + { + QTcpSocket::setSocketError(error); + } + + inline qint64 readData(char *data, qint64 maxSize) + { + return QTcpSocket::readData(data, maxSize); + } + + inline qint64 writeData(const char *data, qint64 maxSize) + { + return QTcpSocket::writeData(data, maxSize); + } +}; +#endif //#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) + +class QLocalSocketPrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QLocalSocket) + +public: + QLocalSocketPrivate(); + void init(); + +#if defined(QT_LOCALSOCKET_TCP) + QLocalUnixSocket* tcpSocket; + bool ownsTcpSocket; + void setSocket(QLocalUnixSocket*); + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); +#elif defined(Q_OS_WIN) + ~QLocalSocketPrivate(); + void destroyPipeHandles(); + void setErrorString(const QString &function); + void _q_notified(); + void _q_canWrite(); + void _q_pipeClosed(); + void _q_emitReadyRead(); + DWORD checkPipeState(); + void startAsyncRead(); + bool completeAsyncRead(); + void checkReadyRead(); + HANDLE handle; + OVERLAPPED overlapped; + QWindowsPipeWriter *pipeWriter; + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + int actualReadBufferSize; + QWinEventNotifier *dataReadNotifier; + QLocalSocket::LocalSocketError error; + bool readSequenceStarted; + bool pendingReadyRead; + bool pipeClosed; + static const qint64 initialReadBufferSize = 4096; +#else + QLocalUnixSocket unixSocket; + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); + void _q_connectToSocket(); + void _q_abortConnectionAttempt(); + void cancelDelayedConnect(); + QSocketNotifier *delayConnect; + QTimer *connectTimer; + int connectingSocket; + QString connectingName; + QIODevice::OpenMode connectingOpenMode; +#endif + + QString serverName; + QString fullServerName; + QLocalSocket::LocalSocketState state; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSOCKET + +#endif // QLOCALSOCKET_P_H + diff --git a/src/network/socket/qlocalsocket_tcp.cpp b/src/network/socket/qlocalsocket_tcp.cpp new file mode 100644 index 0000000000..182d64aeb3 --- /dev/null +++ b/src/network/socket/qlocalsocket_tcp.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** 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 "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qlocalserver.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + tcpSocket(0), + ownsTcpSocket(true), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + setSocket(new QLocalUnixSocket); +} + +void QLocalSocketPrivate::setSocket(QLocalUnixSocket* socket) +{ + if (ownsTcpSocket) + delete tcpSocket; + ownsTcpSocket = false; + tcpSocket = socket; + + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(tcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(tcpSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(tcpSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(tcpSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(tcpSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(tcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + tcpSocket->setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName.clear(); + fullServerName.clear(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error").arg(function); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + tcpSocket->setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + tcpSocket->setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + tcpSocket->setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + tcpSocket->setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + tcpSocket->setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + tcpSocket->setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + tcpSocket->setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + tcpSocket->setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + tcpSocket->setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + tcpSocket->setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + tcpSocket->setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString.clear(); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + d->serverName = name; + const QLatin1String prefix("QLocalServer/"); + if (name.startsWith(prefix)) + d->fullServerName = name; + else + d->fullServerName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + bool ok; + const quint16 port = settings.value(d->fullServerName).toUInt(&ok); + if (!ok) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + d->tcpSocket->connectToHost(QHostAddress::LocalHost, port, openMode); + QIODevice::open(openMode); +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + + // Is our parent a localServer? Then it wants us to use its remote socket. + QLocalServer* localServer = qobject_cast<QLocalServer*>( parent() ); + if (localServer) { + foreach (QObject* child, localServer->children()) { + QTcpSocket* childTcpSocket = qobject_cast<QTcpSocket*>(child); + if (childTcpSocket && childTcpSocket->socketDescriptor() == socketDescriptor) { + d->setSocket( static_cast<QLocalUnixSocket*>(childTcpSocket) ); + return true; + } + } + } + + // We couldn't find the socket in the children list of our server. + // So it might be that the user wants to set a socket descriptor. + return d->tcpSocket->setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->tcpSocket->abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->tcpSocket->bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->tcpSocket->canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->tcpSocket->close(); + d->serverName.clear(); + d->fullServerName.clear(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->tcpSocket->waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->tcpSocket->flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->tcpSocket->disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->tcpSocket->error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->tcpSocket->error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->tcpSocket->setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + return d->tcpSocket->waitForConnected(msec); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->tcpSocket->waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->tcpSocket->waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket_unix.cpp b/src/network/socket/qlocalsocket_unix.cpp new file mode 100644 index 0000000000..da85d917e4 --- /dev/null +++ b/src/network/socket/qlocalsocket_unix.cpp @@ -0,0 +1,581 @@ +/**************************************************************************** +** +** 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 "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qnet_unix_p.h" + +#ifndef QT_NO_LOCALSOCKET + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <qdir.h> +#include <qdebug.h> +#include <qelapsedtimer.h> + +#ifdef Q_OS_VXWORKS +# include <selectLib.h> +#endif + +#define QT_CONNECT_TIMEOUT 30000 + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + delayConnect(0), + connectTimer(0), + connectingSocket(-1), + connectingOpenMode(0), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(&unixSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(&unixSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(&unixSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(&unixSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(&unixSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(&unixSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(&unixSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(&unixSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + unixSocket.setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName.clear(); + fullServerName.clear(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(errno); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + unixSocket.setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + unixSocket.setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + unixSocket.setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + unixSocket.setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + unixSocket.setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + unixSocket.setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + unixSocket.setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + unixSocket.setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + unixSocket.setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + unixSocket.setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + unixSocket.setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString.clear(); + d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + // create the socket + if (-1 == (d->connectingSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0))) { + d->errorOccurred(UnsupportedSocketOperationError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } +#ifndef Q_OS_SYMBIAN + // set non blocking so we can try to connect and it wont wait + int flags = fcntl(d->connectingSocket, F_GETFL, 0); + if (-1 == flags + || -1 == (fcntl(d->connectingSocket, F_SETFL, flags | O_NONBLOCK))) { + d->errorOccurred(UnknownSocketError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } +#endif + + // _q_connectToSocket does the actual connecting + d->connectingName = name; + d->connectingOpenMode = openMode; + d->_q_connectToSocket(); +} + +/*! + \internal + + Tries to connect connectingName and connectingOpenMode + + \sa connectToServer() waitForConnected() + */ +void QLocalSocketPrivate::_q_connectToSocket() +{ + Q_Q(QLocalSocket); + QString connectingPathName; + + // determine the full server path + if (connectingName.startsWith(QLatin1Char('/'))) { + connectingPathName = connectingName; + } else { + connectingPathName = QDir::tempPath(); + connectingPathName += QLatin1Char('/') + connectingName; + } + + struct sockaddr_un name; + name.sun_family = PF_UNIX; + if (sizeof(name.sun_path) < (uint)connectingPathName.toLatin1().size() + 1) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::ServerNotFoundError, function); + return; + } + ::memcpy(name.sun_path, connectingPathName.toLatin1().data(), + connectingPathName.toLatin1().size() + 1); + if (-1 == qt_safe_connect(connectingSocket, (struct sockaddr *)&name, sizeof(name))) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + switch (errno) + { + case EINVAL: + case ECONNREFUSED: + errorOccurred(QLocalSocket::ConnectionRefusedError, function); + break; + case ENOENT: + errorOccurred(QLocalSocket::ServerNotFoundError, function); + break; + case EACCES: + case EPERM: + errorOccurred(QLocalSocket::SocketAccessError, function); + break; + case ETIMEDOUT: + errorOccurred(QLocalSocket::SocketTimeoutError, function); + break; + case EAGAIN: + // Try again later, all of the sockets listening are full + if (!delayConnect) { + delayConnect = new QSocketNotifier(connectingSocket, QSocketNotifier::Write, q); + q->connect(delayConnect, SIGNAL(activated(int)), q, SLOT(_q_connectToSocket())); + } + if (!connectTimer) { + connectTimer = new QTimer(q); + q->connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + connectTimer->start(QT_CONNECT_TIMEOUT); + } + delayConnect->setEnabled(true); + break; + default: + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + return; + } + + // connected! + cancelDelayedConnect(); + + serverName = connectingName; + fullServerName = connectingPathName; + if (unixSocket.setSocketDescriptor(connectingSocket, + QAbstractSocket::ConnectedState, connectingOpenMode)) { + q->QIODevice::open(connectingOpenMode); + q->emit connected(); + } else { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + connectingSocket = -1; + connectingName.clear(); + connectingOpenMode = 0; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + return d->unixSocket.setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +void QLocalSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QLocalSocket); + q->close(); +} + +void QLocalSocketPrivate::cancelDelayedConnect() +{ + if (delayConnect) { + delayConnect->setEnabled(false); + delete delayConnect; + delayConnect = 0; + connectTimer->stop(); + delete connectTimer; + connectTimer = 0; + } +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->unixSocket.abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->unixSocket.bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->unixSocket.canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->unixSocket.close(); + d->cancelDelayedConnect(); + if (d->connectingSocket != -1) + ::close(d->connectingSocket); + d->connectingSocket = -1; + d->connectingName.clear(); + d->connectingOpenMode = 0; + d->serverName.clear(); + d->fullServerName.clear(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->unixSocket.waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->unixSocket.flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->unixSocket.disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->unixSocket.error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->unixSocket.error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->unixSocket.setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(d->connectingSocket, &fds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QElapsedTimer timer; + timer.start(); + while (state() == ConnectingState + && (-1 == msec || timer.elapsed() < msec)) { +#ifdef Q_OS_SYMBIAN + // On Symbian, ready-to-write is signaled when non-blocking socket + // connect is finised. Is ready-to-read really used on other + // UNIX paltforms when using non-blocking AF_UNIX socket? + result = ::select(d->connectingSocket + 1, 0, &fds, 0, &timeout); +#else + result = ::select(d->connectingSocket + 1, &fds, 0, 0, &timeout); +#endif + if (-1 == result && errno != EINTR) { + d->errorOccurred( QLocalSocket::UnknownSocketError, + QLatin1String("QLocalSocket::waitForConnected")); + break; + } + if (result > 0) + d->_q_connectToSocket(); + } + + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->unixSocket.waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->unixSocket.waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qlocalsocket_win.cpp b/src/network/socket/qlocalsocket_win.cpp new file mode 100644 index 0000000000..185f57481a --- /dev/null +++ b/src/network/socket/qlocalsocket_win.cpp @@ -0,0 +1,633 @@ +/**************************************************************************** +** +** 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 "qlocalsocket_p.h" + +#include <private/qthread_p.h> +#include <qcoreapplication.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dataReadNotifier = new QWinEventNotifier(overlapped.hEvent, q); + q->connect(dataReadNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_notified())); +} + +void QLocalSocketPrivate::setErrorString(const QString &function) +{ + Q_Q(QLocalSocket); + BOOL windowsError = GetLastError(); + QLocalSocket::LocalSocketState currentState = state; + + // If the connectToServer fails due to WaitNamedPipe() time-out, assume ConnectionError + if (state == QLocalSocket::ConnectingState && windowsError == ERROR_SEM_TIMEOUT) + windowsError = ERROR_NO_DATA; + + switch (windowsError) { + case ERROR_PIPE_NOT_CONNECTED: + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + error = QLocalSocket::ConnectionError; + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + state = QLocalSocket::UnconnectedState; + break; + case ERROR_FILE_NOT_FOUND: + error = QLocalSocket::ServerNotFoundError; + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + state = QLocalSocket::UnconnectedState; + break; + case ERROR_ACCESS_DENIED: + error = QLocalSocket::SocketAccessError; + errorString = QLocalSocket::tr("%1: Access denied").arg(function); + state = QLocalSocket::UnconnectedState; + break; + default: + error = QLocalSocket::UnknownSocketError; + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(windowsError); +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << errorString; +#endif + state = QLocalSocket::UnconnectedState; + } + + if (currentState != state) { + q->emit stateChanged(state); + if (state == QLocalSocket::UnconnectedState) + q->emit disconnected(); + } + emit q->error(error); +} + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + handle(INVALID_HANDLE_VALUE), + pipeWriter(0), + readBufferMaxSize(0), + actualReadBufferSize(0), + error(QLocalSocket::UnknownSocketError), + readSequenceStarted(false), + pendingReadyRead(false), + pipeClosed(false), + state(QLocalSocket::UnconnectedState) +{ +} + +QLocalSocketPrivate::~QLocalSocketPrivate() +{ + destroyPipeHandles(); + CloseHandle(overlapped.hEvent); +} + +void QLocalSocketPrivate::destroyPipeHandles() +{ + if (handle != INVALID_HANDLE_VALUE) { + DisconnectNamedPipe(handle); + CloseHandle(handle); + } +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState || state() == ConnectingState) + return; + + d->error = QLocalSocket::UnknownSocketError; + d->errorString = QString(); + d->state = ConnectingState; + emit stateChanged(d->state); + if (name.isEmpty()) { + d->error = QLocalSocket::ServerNotFoundError; + setErrorString(QLocalSocket::tr("%1: Invalid name").arg(QLatin1String("QLocalSocket::connectToServer"))); + d->state = UnconnectedState; + emit error(d->error); + emit stateChanged(d->state); + return; + } + + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + d->fullServerName = name; + else + d->fullServerName = pipePath + name; + // Try to open a named pipe + HANDLE localSocket; + forever { + DWORD permissions = (openMode & QIODevice::ReadOnly) ? GENERIC_READ : 0; + permissions |= (openMode & QIODevice::WriteOnly) ? GENERIC_WRITE : 0; + localSocket = CreateFile((const wchar_t *)d->fullServerName.utf16(), // pipe name + permissions, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + FILE_FLAG_OVERLAPPED, + NULL); // no template file + + if (localSocket != INVALID_HANDLE_VALUE) + break; + DWORD error = GetLastError(); + // It is really an error only if it is not ERROR_PIPE_BUSY + if (ERROR_PIPE_BUSY != error) { + break; + } + + // All pipe instances are busy, so wait until connected or up to 5 seconds. + if (!WaitNamedPipe((const wchar_t *)d->fullServerName.utf16(), 5000)) + break; + } + + if (localSocket == INVALID_HANDLE_VALUE) { + d->setErrorString(QLatin1String("QLocalSocket::connectToServer")); + d->fullServerName = QString(); + return; + } + + // we have a valid handle + d->serverName = name; + if (setSocketDescriptor((quintptr)localSocket, ConnectedState, openMode)) { + d->handle = localSocket; + emit connected(); + } +} + +// This is reading from the buffer +qint64 QLocalSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + + if (d->pipeClosed && d->actualReadBufferSize == 0) + return -1; // signal EOF + + qint64 readSoFar; + // If startAsyncRead() read data, copy it to its destination. + if (maxSize == 1 && d->actualReadBufferSize > 0) { + *data = d->readBuffer.getChar(); + d->actualReadBufferSize--; + readSoFar = 1; + } else { + qint64 bytesToRead = qMin(qint64(d->actualReadBufferSize), maxSize); + readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, + qint64(d->readBuffer.nextDataBlockSize())); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + d->actualReadBufferSize -= bytesToReadFromThisBlock; + } + } + + if (d->pipeClosed) { + if (d->actualReadBufferSize == 0) + QTimer::singleShot(0, this, SLOT(_q_pipeClosed())); + } else { + if (!d->readSequenceStarted) + d->startAsyncRead(); + d->checkReadyRead(); + } + + return readSoFar; +} + +/*! + \internal + Schedules or cancels a readyRead() emission depending on actual data availability + */ +void QLocalSocketPrivate::checkReadyRead() +{ + if (actualReadBufferSize > 0) { + if (!pendingReadyRead) { + Q_Q(QLocalSocket); + QTimer::singleShot(0, q, SLOT(_q_emitReadyRead())); + pendingReadyRead = true; + } + } else { + pendingReadyRead = false; + } +} + +/*! + \internal + Reads data from the socket into the readbuffer + */ +void QLocalSocketPrivate::startAsyncRead() +{ + do { + DWORD bytesToRead = checkPipeState(); + if (pipeClosed) + return; + + if (bytesToRead == 0) { + // There are no bytes in the pipe but we need to + // start the overlapped read with some buffer size. + bytesToRead = initialReadBufferSize; + } + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the pipe. + return; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + + readSequenceStarted = true; + if (ReadFile(handle, ptr, bytesToRead, NULL, &overlapped)) { + completeAsyncRead(); + } else { + switch (GetLastError()) { + case ERROR_IO_PENDING: + // This is not an error. We're getting notified, when data arrives. + return; + case ERROR_MORE_DATA: + // This is not an error. The synchronous read succeeded. + // We're connected to a message mode pipe and the message + // didn't fit into the pipe's system buffer. + completeAsyncRead(); + break; + case ERROR_PIPE_NOT_CONNECTED: + { + // It may happen, that the other side closes the connection directly + // after writing data. Then we must set the appropriate socket state. + pipeClosed = true; + Q_Q(QLocalSocket); + emit q->readChannelFinished(); + return; + } + default: + setErrorString(QLatin1String("QLocalSocketPrivate::startAsyncRead")); + return; + } + } + } while (!readSequenceStarted); +} + +/*! + \internal + Sets the correct size of the read buffer after a read operation. + Returns false, if an error occurred or the connection dropped. + */ +bool QLocalSocketPrivate::completeAsyncRead() +{ + ResetEvent(overlapped.hEvent); + readSequenceStarted = false; + + DWORD bytesRead; + if (!GetOverlappedResult(handle, &overlapped, &bytesRead, TRUE)) { + switch (GetLastError()) { + case ERROR_MORE_DATA: + // This is not an error. We're connected to a message mode + // pipe and the message didn't fit into the pipe's system + // buffer. We will read the remaining data in the next call. + break; + case ERROR_PIPE_NOT_CONNECTED: + return false; + default: + setErrorString(QLatin1String("QLocalSocketPrivate::completeAsyncRead")); + return false; + } + } + + actualReadBufferSize += bytesRead; + readBuffer.truncate(actualReadBufferSize); + return true; +} + +qint64 QLocalSocket::writeData(const char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + if (!d->pipeWriter) { + d->pipeWriter = new QWindowsPipeWriter(d->handle, this); + connect(d->pipeWriter, SIGNAL(canWrite()), this, SLOT(_q_canWrite())); + connect(d->pipeWriter, SIGNAL(bytesWritten(qint64)), this, SIGNAL(bytesWritten(qint64))); + d->pipeWriter->start(); + } + return d->pipeWriter->write(data, maxSize); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + if (d->pipeWriter) { + delete d->pipeWriter; + d->pipeWriter = 0; + } + close(); +} + +/*! + \internal + Returns the number of available bytes in the pipe. + Sets QLocalSocketPrivate::pipeClosed to true if the connection is broken. + */ +DWORD QLocalSocketPrivate::checkPipeState() +{ + Q_Q(QLocalSocket); + DWORD bytes; + if (PeekNamedPipe(handle, NULL, 0, NULL, &bytes, NULL)) { + return bytes; + } else { + if (!pipeClosed) { + pipeClosed = true; + emit q->readChannelFinished(); + if (actualReadBufferSize == 0) + QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); + } + } + return 0; +} + +void QLocalSocketPrivate::_q_pipeClosed() +{ + Q_Q(QLocalSocket); + q->close(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + qint64 available = QIODevice::bytesAvailable(); + available += (qint64) d->actualReadBufferSize; + return available; +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return (d->pipeWriter) ? d->pipeWriter->bytesToWrite() : 0; +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + if (state() != ConnectedState) + return false; + return (QIODevice::canReadLine() + || d->readBuffer.indexOf('\n', d->actualReadBufferSize) != -1); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return; + + QIODevice::close(); + d->state = ClosingState; + emit stateChanged(d->state); + if (!d->pipeClosed) + emit readChannelFinished(); + d->serverName = QString(); + d->fullServerName = QString(); + + if (state() != UnconnectedState && bytesToWrite() > 0) { + disconnectFromServer(); + return; + } + d->readSequenceStarted = false; + d->pendingReadyRead = false; + d->pipeClosed = false; + d->destroyPipeHandles(); + d->handle = INVALID_HANDLE_VALUE; + ResetEvent(d->overlapped.hEvent); + d->state = UnconnectedState; + emit stateChanged(d->state); + emit disconnected(); + if (d->pipeWriter) { + delete d->pipeWriter; + d->pipeWriter = 0; + } +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + if (d->pipeWriter) + return d->pipeWriter->waitForWrite(0); + return false; +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + + // Are we still connected? + if (!isValid()) { + // If we have unwritten data, the pipeWriter is still present. + // It must be destroyed before close() to prevent an infinite loop. + delete d->pipeWriter; + d->pipeWriter = 0; + } + + flush(); + if (d->pipeWriter && d->pipeWriter->bytesToWrite() != 0) { + d->state = QLocalSocket::ClosingState; + emit stateChanged(d->state); + } else { + close(); + } +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + return d->error; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + d->readBuffer.clear(); + d->actualReadBufferSize = 0; + QIODevice::open(openMode); + d->handle = (int*)socketDescriptor; + d->state = socketState; + emit stateChanged(d->state); + if (d->state == ConnectedState && openMode.testFlag(QIODevice::ReadOnly)) { + d->startAsyncRead(); + d->checkReadyRead(); + } + return true; +} + +void QLocalSocketPrivate::_q_canWrite() +{ + Q_Q(QLocalSocket); + if (state == QLocalSocket::ClosingState) + q->close(); +} + +void QLocalSocketPrivate::_q_notified() +{ + Q_Q(QLocalSocket); + if (!completeAsyncRead()) { + pipeClosed = true; + emit q->readChannelFinished(); + if (actualReadBufferSize == 0) + QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); + return; + } + startAsyncRead(); + pendingReadyRead = false; + emit q->readyRead(); +} + +void QLocalSocketPrivate::_q_emitReadyRead() +{ + if (pendingReadyRead) { + Q_Q(QLocalSocket); + pendingReadyRead = false; + emit q->readyRead(); + } +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return (quintptr)d->handle; +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->readBufferMaxSize; +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->readBufferMaxSize = size; +} + +bool QLocalSocket::waitForConnected(int msecs) +{ + Q_UNUSED(msecs); + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return false; + if (!openMode().testFlag(QIODevice::ReadOnly)) { + qWarning("QLocalSocket::waitForDisconnected isn't supported for write only pipes."); + return false; + } + QIncrementalSleepTimer timer(msecs); + forever { + d->checkPipeState(); + if (d->pipeClosed) + close(); + if (state() == UnconnectedState) + return true; + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + return false; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->handle != INVALID_HANDLE_VALUE; +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + + if (bytesAvailable() > 0) + return true; + + if (d->state != QLocalSocket::ConnectedState) + return false; + + // We already know that the pipe is gone, but did not enter the event loop yet. + if (d->pipeClosed) { + close(); + return false; + } + + Q_ASSERT(d->readSequenceStarted); + DWORD result = WaitForSingleObject(d->overlapped.hEvent, msecs == -1 ? INFINITE : msecs); + switch (result) { + case WAIT_OBJECT_0: + d->_q_notified(); + // We just noticed that the pipe is gone. + if (d->pipeClosed) { + close(); + return false; + } + return true; + case WAIT_TIMEOUT: + return false; + } + + qWarning("QLocalSocket::waitForReadyRead WaitForSingleObject failed with error code %d.", int(GetLastError())); + return false; +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(const QLocalSocket); + if (!d->pipeWriter) + return false; + + // Wait for the pipe writer to acknowledge that it has + // written. This will succeed if either the pipe writer has + // already written the data, or if it manages to write data + // within the given timeout. + return d->pipeWriter->waitForWrite(msecs); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp new file mode 100644 index 0000000000..f5a88e2cc7 --- /dev/null +++ b/src/network/socket/qnativesocketengine.cpp @@ -0,0 +1,1258 @@ +/**************************************************************************** +** +** 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 QNATIVESOCKETENGINE_DEBUG + +/*! \class QNativeSocketEngine + \internal + + \brief The QNativeSocketEngine class provides low level access to a socket. + + \reentrant + \ingroup network + \inmodule QtNetwork + + QtSocketLayer provides basic socket functionality provided by the + operating system. It also keeps track of what state the socket is + in, and which errors that occur. + + The classes QTcpSocket, QUdpSocket and QTcpServer provide a + higher level API, and are in general more useful for the common + application. + + There are two main ways of initializing the a QNativeSocketEngine; either + create a new socket by passing the socket type (TcpSocket or + UdpSocket) and network layer protocol (IPv4Protocol or + IPv6Protocol) to initialize(), or pass an existing socket + descriptor and have QNativeSocketEngine determine the type and protocol + itself. The native socket descriptor can later be fetched by + calling socketDescriptor(). The socket is made non-blocking, but + blocking behavior can still be achieved by calling waitForRead() + and waitForWrite(). isValid() can be called to check if the socket + has been successfully initialized and is ready to use. + + To connect to a host, determine its address and pass this and the + port number to connectToHost(). The socket can then be used as a + TCP or UDP client. Otherwise; bind(), listen() and accept() are + used to have the socket function as a TCP or UDP server. Call + close() to close the socket. + + bytesAvailable() is called to determine how much data is available + for reading. read() and write() are used by both TCP and UDP + clients to exchange data with the connected peer. UDP clients can + also call hasMoreDatagrams(), nextDatagramSize(), + readDatagram(), and writeDatagram(). + + Call state() to determine the state of the socket, for + example, ListeningState or ConnectedState. socketType() tells + whether the socket is a TCP socket or a UDP socket, or if the + socket type is unknown. protocol() is used to determine the + socket's network layer protocol. + + localAddress(), localPort() are called to find the address and + port that are currently bound to the socket. If the socket is + connected, peerAddress() and peerPort() determine the address and + port of the connected peer. + + Finally, if any function should fail, error() and + errorString() can be called to determine the cause of the error. +*/ + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qnetworkinterface.h> + +#include "qnativesocketengine_p.h" +#include <private/qthread_p.h> +#include <private/qobject_p.h> + +#if !defined(QT_NO_NETWORKPROXY) +# include "qnetworkproxy.h" +# include "qabstractsocket.h" +# include "qtcpserver.h" +#endif + +QT_BEGIN_NAMESPACE + +//#define QNATIVESOCKETENGINE_DEBUG + +#define Q_VOID + +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) +#define Q_TR(a) QT_TRANSLATE_NOOP(QNativeSocketEngine, a) + +/*! \internal + Constructs the private class and initializes all data members. + + On Windows, WSAStartup is called "recursively" for every + concurrent QNativeSocketEngine. This is safe, because WSAStartup and + WSACleanup are reference counted. +*/ +QNativeSocketEnginePrivate::QNativeSocketEnginePrivate() : + socketDescriptor(-1), + readNotifier(0), + writeNotifier(0), + exceptNotifier(0) +{ +} + +/*! \internal + Destructs the private class. +*/ +QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate() +{ +} + +/*! \internal + + Sets the error and error string if not set already. The only + interesting error is the first one that occurred, and not the last + one. +*/ +void QNativeSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + // Another exception is the way the waitFor*() functions set + // an error when a timeout occurs. After the call to setError() + // they reset the hasSetSocketError to false + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize non-blocking socket"); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to initialize broadcast socket"); + break; + case NoIpV6ErrorString: + socketErrorString = QNativeSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support"); + break; + case RemoteHostClosedErrorString: + socketErrorString = QNativeSocketEngine::tr("The remote host closed the connection"); + break; + case TimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Network operation timed out"); + break; + case ResourceErrorString: + socketErrorString = QNativeSocketEngine::tr("Out of resources"); + break; + case OperationUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Unsupported socket operation"); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QNativeSocketEngine::tr("Protocol type not supported"); + break; + case InvalidSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Invalid socket descriptor"); + break; + case HostUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Host unreachable"); + break; + case NetworkUnreachableErrorString: + socketErrorString = QNativeSocketEngine::tr("Network unreachable"); + break; + case AccessErrorString: + socketErrorString = QNativeSocketEngine::tr("Permission denied"); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection timed out"); + break; + case ConnectionRefusedErrorString: + socketErrorString = QNativeSocketEngine::tr("Connection refused"); + break; + case AddressInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("The bound address is already in use"); + break; + case AddressNotAvailableErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is not available"); + break; + case AddressProtectedErrorString: + socketErrorString = QNativeSocketEngine::tr("The address is protected"); + break; + case DatagramTooLargeErrorString: + socketErrorString = QNativeSocketEngine::tr("Datagram was too large to send"); + break; + case SendDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to send a message"); + break; + case ReceiveDatagramErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to receive a message"); + break; + case WriteErrorString: + socketErrorString = QNativeSocketEngine::tr("Unable to write"); + break; + case ReadErrorString: + socketErrorString = QNativeSocketEngine::tr("Network error"); + break; + case PortInuseErrorString: + socketErrorString = QNativeSocketEngine::tr("Another socket is already listening on the same port"); + break; + case NotSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Operation on non-socket"); + break; + case InvalidProxyTypeString: + socketErrorString = QNativeSocketEngine::tr("The proxy type is invalid for this operation"); + break; + case UnknownSocketErrorString: + socketErrorString = QNativeSocketEngine::tr("Unknown error"); + break; + } +} + +bool QNativeSocketEnginePrivate::checkProxy(const QHostAddress &address) +{ + if (address == QHostAddress::LocalHost || address == QHostAddress::LocalHostIPv6) + return true; + +#if !defined(QT_NO_NETWORKPROXY) + QObject *parent = q_func()->parent(); + QNetworkProxy proxy; + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(parent)) { + proxy = socket->proxy(); + } else if (QTcpServer *server = qobject_cast<QTcpServer *>(parent)) { + proxy = server->proxy(); + } else { + // no parent -> no proxy + return true; + } + + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() != QNetworkProxy::DefaultProxy && + proxy.type() != QNetworkProxy::NoProxy) { + // QNativeSocketEngine doesn't do proxies + setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::InvalidProxyTypeString); + return false; + } +#endif + + return true; +} + +/*! + Constructs a QNativeSocketEngine. + + \sa initialize() +*/ +QNativeSocketEngine::QNativeSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent) +{ +} + +/*! + Destructs a QNativeSocketEngine. +*/ +QNativeSocketEngine::~QNativeSocketEngine() +{ + close(); +} + +/*! + Initializes a QNativeSocketEngine by creating a new socket of type \a + socketType and network layer protocol \a protocol. Returns true on + success; otherwise returns false. + + If the socket was already initialized, this function closes the + socket before reeinitializing it. + + The new socket is non-blocking, and for UDP sockets it's also + broadcast enabled. +*/ +bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QNativeSocketEngine); + if (isValid()) + close(); + +#if defined(QT_NO_IPV6) + if (protocol == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + + // Create the socket + if (!d->createNewSocket(socketType, protocol)) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString typeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + QString protocolStr = QLatin1String("UnknownProtocol"); + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + qDebug("QNativeSocketEngine::initialize(type == %s, protocol == %s) failed: %s", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + + + // Make sure we receive out-of-band data + if (socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data"); + } + + // Before Qt 4.6, we always set the send and receive buffer size to 49152 as + // this was found to be an optimal value. However, modern OS + // all have some kind of auto tuning for this and we therefore don't set + // this explictly anymore. + // If it introduces any performance regressions for Qt 4.6.x (x > 0) then + // it will be put back in. + // + // You can use tests/manual/qhttpnetworkconnection to test HTTP download speed + // with this. + // + // pre-4.6: + // setReceiveBufferSize(49152); + // setSendBufferSize(49152); + + d->socketType = socketType; + d->socketProtocol = protocol; + return true; +} + +/*! \overload + + Initializes the socket using \a socketDescriptor instead of + creating a new one. The socket type and network layer protocol are + determined automatically. The socket's state is set to \a + socketState. + + If the socket type is either TCP or UDP, it is made non-blocking. + UDP sockets are also broadcast enabled. + */ +bool QNativeSocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QNativeSocketEngine); + + if (isValid()) + close(); + + d->socketDescriptor = socketDescriptor; + + // determine socket type and protocol + if (!d->fetchConnectionParameters()) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::initialize(socketDescriptor == %i) failed: %s", + socketDescriptor, d->socketErrorString.toLatin1().constData()); +#endif + d->socketDescriptor = -1; + return false; + } + + if (d->socketType != QAbstractSocket::UnknownSocketType) { + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (d->socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + } + + d->socketState = socketState; + return true; +} + +/*! + Returns true if the socket is valid; otherwise returns false. A + socket is valid if it has not been successfully initialized, or if + it has been closed. +*/ +bool QNativeSocketEngine::isValid() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor != -1; +} + +/*! + Returns the native socket descriptor. Any use of this descriptor + stands the risk of being non-portable. +*/ +int QNativeSocketEngine::socketDescriptor() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor; +} + +/*! + Connects to the IP address and port specified by \a address and \a + port. If the connection is established, this function returns true + and the socket enters ConnectedState. Otherwise, false is + returned. + + If false is returned, state() should be called to see if the + socket is in ConnectingState. If so, a delayed TCP connection is + taking place, and connectToHost() must be called again later to + determine if the connection was established successfully or + not. The second connection attempt must be made when the socket is + ready for writing. This state can be determined either by + connecting a QSocketNotifier to the socket descriptor returned by + socketDescriptor(), or by calling the blocking function + waitForWrite(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 0 + + Otherwise, error() should be called to determine the cause of the + error. +*/ +bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATES(QNativeSocketEngine::connectToHost(), + QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); + + d->peerAddress = address; + d->peerPort = port; + bool connected = d->nativeConnect(address, port); + if (connected) + d->fetchConnectionParameters(); + + return connected; +} + +/*! + If there's a connection activity on the socket, process it. Then + notify our parent if there really was activity. +*/ +void QNativeSocketEngine::connectionNotification() +{ + Q_D(QNativeSocketEngine); + Q_ASSERT(state() == QAbstractSocket::ConnectingState); + + connectToHost(d->peerAddress, d->peerPort); + if (state() != QAbstractSocket::ConnectingState) { + // we changed states + QAbstractSocketEngine::connectionNotification(); + } +} + +/*! + Connects to the remote host name given by \a name on port \a + port. When this function is called, the upper-level will not + perform a hostname lookup. + + The native socket engine does not support this operation, + but some other socket engines (notably proxy-based ones) do. +*/ +bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_UNUSED(name); + Q_UNUSED(port); + Q_D(QNativeSocketEngine); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + return false; +} + +/*! + Binds the socket to the address \a address and port \a + port. Returns true on success; otherwise false is returned. The + port may be 0, in which case an arbitrary unused port is assigned + automatically by the operating system. + + Servers call this function to set up the server's address and + port. TCP servers must in addition call listen() after bind(). +*/ +bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bind(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATE(QNativeSocketEngine::bind(), QAbstractSocket::UnconnectedState, false); + + if (!d->nativeBind(address, port)) + return false; + + d->fetchConnectionParameters(); + return true; +} + +/*! + Prepares a TCP server for accepting incoming connections. This + function must be called after bind(), and only by TCP sockets. + + After this function has been called, pending client connections + are detected by checking if the socket is ready for reading. This + can be done by either creating a QSocketNotifier, passing the + socket descriptor returned by socketDescriptor(), or by calling + the blocking function waitForRead(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 1 + + \sa bind(), accept() +*/ +bool QNativeSocketEngine::listen() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false); + Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + + // We're using a backlog of 50. Most modern kernels support TCP + // syncookies by default, and if they do, the backlog is ignored. + // When there is no support for TCP syncookies, this value is + // fine. + return d->nativeListen(50); +} + +/*! + Accepts a pending connection from the socket, which must be in + ListeningState, and returns its socket descriptor. If no pending + connections are available, -1 is returned. + + \sa bind(), listen() +*/ +int QNativeSocketEngine::accept() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); + Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, false); + Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, false); + + return d->nativeAccept(); +} + +#ifndef QT_NO_NETWORKINTERFACE + +/*! + \since 4.8 +*/ +bool QNativeSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::joinMulticastGroup(), false); + Q_CHECK_STATE(QNativeSocketEngine::joinMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::joinMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->nativeJoinMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 +*/ +bool QNativeSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::leaveMulticastGroup(), false); + Q_CHECK_STATE(QNativeSocketEngine::leaveMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::leaveMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->nativeLeaveMulticastGroup(groupAddress, iface); +} + +/*! \since 4.8 */ +QNetworkInterface QNativeSocketEngine::multicastInterface() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::multicastInterface(), QNetworkInterface()); + Q_CHECK_TYPE(QNativeSocketEngine::multicastInterface(), QAbstractSocket::UdpSocket, QNetworkInterface()); + return d->nativeMulticastInterface(); +} + +/*! \since 4.8 */ +bool QNativeSocketEngine::setMulticastInterface(const QNetworkInterface &iface) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setMulticastInterface(), false); + Q_CHECK_TYPE(QNativeSocketEngine::setMulticastInterface(), QAbstractSocket::UdpSocket, false); + return d->nativeSetMulticastInterface(iface); +} + +#endif // QT_NO_NETWORKINTERFACE + +/*! + Returns the number of bytes that are currently available for + reading. On error, -1 is returned. + + For UDP sockets, this function returns the accumulated size of all + pending datagrams, and it is therefore more useful for UDP sockets + to call hasPendingDatagrams() and pendingDatagramSize(). +*/ +qint64 QNativeSocketEngine::bytesAvailable() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bytesAvailable(), -1); + Q_CHECK_NOT_STATE(QNativeSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, false); + + return d->nativeBytesAvailable(); +} + +/*! + Returns true if there is at least one datagram pending. This + function is only called by UDP sockets, where a datagram can have + a size of 0. TCP sockets call bytesAvailable(). +*/ +bool QNativeSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::hasPendingDatagrams(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UnconnectedState, false); + Q_CHECK_TYPE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + + return d->nativeHasPendingDatagrams(); +} + +/*! + Returns the size of the pending datagram, or -1 if no datagram is + pending. A datagram size of 0 is perfectly valid. This function is + called by UDP sockets before receiveMessage(). For TCP sockets, + call bytesAvailable(). +*/ +qint64 QNativeSocketEngine::pendingDatagramSize() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::pendingDatagramSize(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::pendingDatagramSize(), QAbstractSocket::UdpSocket, false); + + return d->nativePendingDatagramSize(); +} + +/*! + Reads up to \a maxSize bytes of a datagram from the socket, + stores it in \a data and returns the number of bytes read. The + address and port of the sender are stored in \a address and \a + port. If either of these pointers is 0, the corresponding value is + discarded. + + To avoid unnecessarily loss of data, call pendingDatagramSize() to + determine the size of the pending message before reading it. If \a + maxSize is too small, the rest of the datagram will be lost. + + Returns -1 if an error occurred. + + \sa hasPendingDatagrams() +*/ +qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::readDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, false); + + return d->nativeReceiveDatagram(data, maxSize, address, port); +} + +/*! + Writes a UDP datagram of size \a size bytes to the socket from + \a data to the address \a host on port \a port, and returns the + number of bytes written, or -1 if an error occurred. + + Only one datagram is sent, and if there is too much data to fit + into a single datagram, the operation will fail and error() + will return QAbstractSocket::DatagramTooLargeError. Operating systems impose an + upper limit to the size of a datagram, but this size is different + on almost all platforms. Sending large datagrams is in general + disadvised, as even if they are sent successfully, they are likely + to be fragmented before arriving at their destination. + + Experience has shown that it is in general safe to send datagrams + no larger than 512 bytes. + + \sa readDatagram() +*/ +qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 size, + const QHostAddress &host, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::writeDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + return d->nativeSendDatagram(data, size, host, port); +} + +/*! + Writes a block of \a size bytes from \a data to the socket. + Returns the number of bytes written, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::write(const char *data, qint64 size) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -1); + Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1); + return d->nativeWrite(data, size); +} + + +qint64 QNativeSocketEngine::bytesToWrite() const +{ + return 0; +} + +/*! + Reads up to \a maxSize bytes into \a data from the socket. + Returns the number of bytes read, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1); + Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); + + qint64 readBytes = d->nativeRead(data, maxSize); + + // Handle remote close + if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { + d->setError(QAbstractSocket::RemoteHostClosedError, + QNativeSocketEnginePrivate::RemoteHostClosedErrorString); + close(); + return -1; + } else if (readBytes == -1) { + if (!d->hasSetSocketError) { + d->hasSetSocketError = true; + d->socketError = QAbstractSocket::NetworkError; + d->socketErrorString = qt_error_string(); + } + close(); + return -1; + } + return readBytes; +} + +/*! + Closes the socket. In order to use the socket again, initialize() + must be called. +*/ +void QNativeSocketEngine::close() +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) + d->readNotifier->setEnabled(false); + if (d->writeNotifier) + d->writeNotifier->setEnabled(false); + if (d->exceptNotifier) + d->exceptNotifier->setEnabled(false); + + if(d->socketDescriptor != -1) { + d->nativeClose(); + d->socketDescriptor = -1; + } + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); + if (d->readNotifier) { + qDeleteInEventHandler(d->readNotifier); + d->readNotifier = 0; + } + if (d->writeNotifier) { + qDeleteInEventHandler(d->writeNotifier); + d->writeNotifier = 0; + } + if (d->exceptNotifier) { + qDeleteInEventHandler(d->exceptNotifier); + d->exceptNotifier = 0; + } +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + reading. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for reading; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForRead() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, true); + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + writing. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for writing; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForWrite() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForWrite(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, false); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } else { + int value = 0; + int valueSize = sizeof(value); + if (::getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAETIMEDOUT) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAEHOSTUNREACH) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + } + } +#endif + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForReadOrWrite(), + QAbstractSocket::UnconnectedState, false); + + int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (checkWrite && ((readyToWrite && *readyToWrite) || !readyToWrite) && ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } else { + int value = 0; + int valueSize = sizeof(value); + if (::getsockopt(d->socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAETIMEDOUT) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } else if (value == WSAEHOSTUNREACH) { + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + return false; + } + } + } +#endif + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Returns the size of the operating system's socket receive + buffer. Depending on the operating system, this size may be + different from what has been set earlier with + setReceiveBufferSize(). +*/ +qint64 QNativeSocketEngine::receiveBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::receiveBufferSize(), -1); + return option(ReceiveBufferSocketOption); +} + +/*! + Sets the size of the operating system receive buffer to \a size. + + For clients, this should be set before connectToHost() is called; + otherwise it will have no effect. For servers, it should be called + before listen(). + + The operating system receive buffer size effectively limits two + things: how much data can be in transit at any one moment, and how + much data can be received in one iteration of the main event loop. + Setting the size of the receive buffer may have an impact on the + socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setReceiveBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setReceiveBufferSize(), Q_VOID); + setOption(ReceiveBufferSocketOption, size); +} + +/*! + Returns the size of the operating system send buffer. Depending on + the operating system, this size may be different from what has + been set earlier with setSendBufferSize(). +*/ +qint64 QNativeSocketEngine::sendBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), -1); + return option(SendBufferSocketOption); +} + +/*! + Sets the size of the operating system send buffer to \a size. + + The operating system send buffer size effectively limits how much + data can be in transit at any one moment. Setting the size of the + send buffer may have an impact on the socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setSendBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), Q_VOID); + setOption(SendBufferSocketOption, size); +} + + +/*! + Sets the option \a option to the value \a value. +*/ +bool QNativeSocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QNativeSocketEngine); + return d->setOption(option, value); +} + +/*! + Returns the value of the option \a socketOption. +*/ +int QNativeSocketEngine::option(SocketOption socketOption) const +{ + Q_D(const QNativeSocketEngine); + return d->option(socketOption); +} + +bool QNativeSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->readNotifier && d->readNotifier->isEnabled(); +} + +/* + \internal + \class QReadNotifier + \brief The QReadNotifer class is used to improve performance. + + QReadNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QReadNotifier : public QSocketNotifier +{ +public: + QReadNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Read, parent) + { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QReadNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + engine->readNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +/* + \internal + \class QWriteNotifier + \brief The QWriteNotifer class is used to improve performance. + + QWriteNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QWriteNotifier : public QSocketNotifier +{ +public: + QWriteNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Write, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QWriteNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->writeNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +class QExceptionNotifier : public QSocketNotifier +{ +public: + QExceptionNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Exception, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QExceptionNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->exceptionNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +void QNativeSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) { + d->readNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->readNotifier = new QReadNotifier(d->socketDescriptor, this); + d->readNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->writeNotifier && d->writeNotifier->isEnabled(); +} + +void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->writeNotifier) { + d->writeNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this); + d->writeNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->exceptNotifier && d->exceptNotifier->isEnabled(); +} + +void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->exceptNotifier) { + d->exceptNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->exceptNotifier = new QExceptionNotifier(d->socketDescriptor, this); + d->exceptNotifier->setEnabled(true); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h new file mode 100644 index 0000000000..35054fb414 --- /dev/null +++ b/src/network/socket/qnativesocketengine_p.h @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** 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 QNATIVESOCKETENGINE_P_H +#define QNATIVESOCKETENGINE_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#ifndef Q_OS_WIN +# include "qplatformdefs.h" +#else +# include <winsock2.h> +#endif + +QT_BEGIN_NAMESPACE + +// Use our own defines and structs which we know are correct +# define QT_SS_MAXSIZE 128 +# define QT_SS_ALIGNSIZE (sizeof(qint64)) +# define QT_SS_PAD1SIZE (QT_SS_ALIGNSIZE - sizeof (short)) +# define QT_SS_PAD2SIZE (QT_SS_MAXSIZE - (sizeof (short) + QT_SS_PAD1SIZE + QT_SS_ALIGNSIZE)) +struct qt_sockaddr_storage { + short ss_family; + char __ss_pad1[QT_SS_PAD1SIZE]; + qint64 __ss_align; + char __ss_pad2[QT_SS_PAD2SIZE]; +}; + +// sockaddr_in6 size changed between old and new SDK +// Only the new version is the correct one, so always +// use this structure. +struct qt_in6_addr { + quint8 qt_s6_addr[16]; +}; +struct qt_sockaddr_in6 { + short sin6_family; /* AF_INET6 */ + quint16 sin6_port; /* Transport level port number */ + quint32 sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + quint32 sin6_scope_id; /* set of interfaces for a scope */ +}; + +union qt_sockaddr { + sockaddr a; + sockaddr_in a4; + qt_sockaddr_in6 a6; + qt_sockaddr_storage storage; +}; + +class QNativeSocketEnginePrivate; +#ifndef QT_NO_NETWORKINTERFACE +class QNetworkInterface; +#endif + +class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QNativeSocketEngine(QObject *parent = 0); + ~QNativeSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 bytesToWrite() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public Q_SLOTS: + // non-virtual override; + void connectionNotification(); + +private: + Q_DECLARE_PRIVATE(QNativeSocketEngine) + Q_DISABLE_COPY(QNativeSocketEngine) +}; + +#ifdef Q_OS_WIN +class QWindowsSockInit +{ +public: + QWindowsSockInit(); + ~QWindowsSockInit(); + int version; +}; +#endif + +class QSocketNotifier; + +class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QNativeSocketEngine) +public: + QNativeSocketEnginePrivate(); + ~QNativeSocketEnginePrivate(); + + int socketDescriptor; + + QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier; + +#ifdef Q_OS_WIN + QWindowsSockInit winSock; +#endif + + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + + UnknownSocketErrorString = -1 + }; + + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + // native functions + int option(QNativeSocketEngine::SocketOption option) const; + bool setOption(QNativeSocketEngine::SocketOption option, int value); + + bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol); + + bool nativeConnect(const QHostAddress &address, quint16 port); + bool nativeBind(const QHostAddress &address, quint16 port); + bool nativeListen(int backlog); + int nativeAccept(); +#ifndef QT_NO_NETWORKINTERFACE + bool nativeJoinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface nativeMulticastInterface() const; + bool nativeSetMulticastInterface(const QNetworkInterface &iface); +#endif + qint64 nativeBytesAvailable() const; + + bool nativeHasPendingDatagrams() const; + qint64 nativePendingDatagramSize() const; + qint64 nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port); + qint64 nativeSendDatagram(const char *data, qint64 length, + const QHostAddress &host, quint16 port); + qint64 nativeRead(char *data, qint64 maxLength); + qint64 nativeWrite(const char *data, qint64 length); + int nativeSelect(int timeout, bool selectForRead) const; + int nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const; + + void nativeClose(); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); +}; + +QT_END_NAMESPACE + +#endif // QNATIVESOCKETENGINE_P_H diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp new file mode 100644 index 0000000000..43184271e5 --- /dev/null +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -0,0 +1,1125 @@ +/**************************************************************************** +** +** 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 QNATIVESOCKETENGINE_DEBUG +#include "qnativesocketengine_p.h" +#include "private/qnet_unix_p.h" +#include "qiodevice.h" +#include "qhostaddress.h" +#include "qelapsedtimer.h" +#include "qvarlengtharray.h" +#include "qnetworkinterface.h" +#include <time.h> +#include <errno.h> +#include <fcntl.h> +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifdef QT_LINUXBASE +#include <arpa/inet.h> +#endif + +#if defined QNATIVESOCKETENGINE_DEBUG +#include <qstring.h> +#include <ctype.h> +#endif + +#include <netinet/tcp.h> + +QT_BEGIN_NAMESPACE + +#if defined QNATIVESOCKETENGINE_DEBUG + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +#endif + +static void qt_ignore_sigpipe() +{ +#ifndef Q_NO_POSIX_SIGNALS + // Set to ignore SIGPIPE once only. + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + } +#else + // Posix signals are not supported by the underlying platform + // so we don't need to ignore sigpipe signal explicitly +#endif +} + +/* + Extracts the port and address from a sockaddr, and stores them in + \a port and \a addr if they are non-null. +*/ +static inline void qt_socket_getPortAndAddress(const qt_sockaddr *s, quint16 *port, QHostAddress *addr) +{ +#if !defined(QT_NO_IPV6) + if (s->a.sa_family == AF_INET6) { + Q_IPV6ADDR tmp; + memcpy(&tmp, &s->a6.sin6_addr, sizeof(tmp)); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(tmp); + *addr = tmpAddress; +#ifndef QT_NO_IPV6IFNAME + char scopeid[IFNAMSIZ]; + if (::if_indextoname(s->a6.sin6_scope_id, scopeid)) { + addr->setScopeId(QLatin1String(scopeid)); + } else +#endif + addr->setScopeId(QString::number(s->a6.sin6_scope_id)); + } + if (port) + *port = ntohs(s->a6.sin6_port); + return; + } +#endif + if (port) + *port = ntohs(s->a4.sin_port); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(ntohl(s->a4.sin_addr.s_addr)); + *addr = tmpAddress; + } +} + +/*! \internal + + Creates and returns a new socket descriptor of type \a socketType + and \a socketProtocol. Returns -1 on failure. +*/ +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ +#ifndef QT_NO_IPV6 + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; +#else + Q_UNUSED(socketProtocol); + int protocol = AF_INET; +#endif + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + + int socket = qt_safe_socket(protocol, type, 0); + + if (socket <= 0) { + switch (errno) { + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + case EINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString); + break; + case ENFILE: + case EMFILE: + case ENOBUFS: + case ENOMEM: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case EACCES: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + break; + default: + break; + } + + return false; + } + + socketDescriptor = socket; + return true; +} + +/* + Returns the value of the socket option \a opt. +*/ +int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) const +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return -1; + + int n = -1; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + break; + case QNativeSocketEngine::BroadcastSocketOption: + break; + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + return true; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + case QNativeSocketEngine::LowDelayOption: + level = IPPROTO_TCP; + n = TCP_NODELAY; + break; + case QNativeSocketEngine::KeepAliveOption: + n = SO_KEEPALIVE; + break; + case QNativeSocketEngine::MulticastTtlOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (::getsockopt(socketDescriptor, level, n, (char *) &v, &len) != -1) + return v; + + return -1; +} + + +/* + Sets the socket option \a opt to \a v. +*/ +bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v) +{ + Q_Q(QNativeSocketEngine); + if (!q->isValid()) + return false; + + int n = 0; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: { + // Make the socket nonblocking. +#if !defined(Q_OS_VXWORKS) + int flags = ::fcntl(socketDescriptor, F_GETFL, 0); + if (flags == -1) { +#ifdef QNATIVESOCKETENGINE_DEBUG + perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_GETFL) failed"); +#endif + return false; + } + if (::fcntl(socketDescriptor, F_SETFL, flags | O_NONBLOCK) == -1) { +#ifdef QNATIVESOCKETENGINE_DEBUG + perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_SETFL) failed"); +#endif + return false; + } +#else // Q_OS_VXWORKS + int onoff = 1; + + if (qt_safe_ioctl(socketDescriptor, FIONBIO, &onoff) < 0) { + +#ifdef QNATIVESOCKETENGINE_DEBUG + perror("QNativeSocketEnginePrivate::setOption(): ioctl(FIONBIO, 1) failed"); +#endif + return false; + } +#endif // Q_OS_VXWORKS + return true; + } + case QNativeSocketEngine::AddressReusable: +#if defined(SO_REUSEPORT) + n = SO_REUSEPORT; +#else + n = SO_REUSEADDR; +#endif + break; + case QNativeSocketEngine::BindExclusively: + return true; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + case QNativeSocketEngine::LowDelayOption: + level = IPPROTO_TCP; + n = TCP_NODELAY; + break; + case QNativeSocketEngine::KeepAliveOption: + n = SO_KEEPALIVE; + break; + case QNativeSocketEngine::MulticastTtlOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + return ::setsockopt(socketDescriptor, level, n, (char *) &v, sizeof(v)) == 0; +} + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &addr, quint16 port) +{ +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug("QNativeSocketEnginePrivate::nativeConnect() : %d ", socketDescriptor); +#endif + + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); + + QString scopeid = addr.scopeId(); + bool ok; + sockAddrIPv6.sin6_scope_id = scopeid.toInt(&ok); +#ifndef QT_NO_IPV6IFNAME + if (!ok) + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(scopeid.toLatin1()); +#endif + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &ip6, sizeof(ip6)); + + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#if 0 + {} +#endif +#endif + if (addr.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(addr.toIPv4Address()); + + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int connectResult = qt_safe_connect(socketDescriptor, sockAddrPtr, sockAddrSize); + if (connectResult == -1) { + switch (errno) { + case EISCONN: + socketState = QAbstractSocket::ConnectedState; + break; + case ECONNREFUSED: + case EINVAL: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case ETIMEDOUT: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case EHOSTUNREACH: + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case ENETUNREACH: + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case EADDRINUSE: + setError(QAbstractSocket::NetworkError, AddressInuseErrorString); + break; + case EINPROGRESS: + case EALREADY: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + socketState = QAbstractSocket::ConnectingState; + break; + case EAGAIN: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case EACCES: + case EPERM: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case EAFNOSUPPORT: + case EBADF: + case EFAULT: + case ENOTSOCK: + socketState = QAbstractSocket::UnconnectedState; + default: + break; + } + + if (socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)", + addr.toString().toLatin1().constData(), port, + socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true", + addr.toString().toLatin1().constData(), port); +#endif + + socketState = QAbstractSocket::ConnectedState; + return true; +} + +bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &address, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); +#ifndef QT_NO_IPV6IFNAME + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(address.scopeId().toLatin1().data()); +#else + sockAddrIPv6.sin6_scope_id = address.scopeId().toInt(); +#endif + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(address.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int bindResult = QT_SOCKET_BIND(socketDescriptor, sockAddrPtr, sockAddrSize); + + if (bindResult < 0) { + switch(errno) { + case EADDRINUSE: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case EACCES: + setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString); + break; + case EINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, OperationUnsupportedErrorString); + break; + case EADDRNOTAVAIL: + setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + socketState = QAbstractSocket::BoundState; + return true; +} + +bool QNativeSocketEnginePrivate::nativeListen(int backlog) +{ + if (qt_safe_listen(socketDescriptor, backlog) < 0) { + switch (errno) { + case EADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = qt_safe_accept(socketDescriptor, 0, 0); + + return acceptedDescriptor; +} + +#ifndef QT_NO_NETWORKINTERFACE + +static bool multicastMembershipHelper(QNativeSocketEnginePrivate *d, + int how6, + int how4, + const QHostAddress &groupAddress, + const QNetworkInterface &interface) +{ + int level = 0; + int sockOpt = 0; + void *sockArg; + int sockArgSize; + + ip_mreq mreq4; +#ifndef QT_NO_IPV6 + ipv6_mreq mreq6; + + if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + sockOpt = how6; + sockArg = &mreq6; + sockArgSize = sizeof(mreq6); + memset(&mreq6, 0, sizeof(mreq6)); + Q_IPV6ADDR ip6 = groupAddress.toIPv6Address(); + memcpy(&mreq6.ipv6mr_multiaddr, &ip6, sizeof(ip6)); + mreq6.ipv6mr_interface = interface.index(); + } else +#endif + if (groupAddress.protocol() == QAbstractSocket::IPv4Protocol) { + level = IPPROTO_IP; + sockOpt = how4; + sockArg = &mreq4; + sockArgSize = sizeof(mreq4); + memset(&mreq4, 0, sizeof(mreq4)); + mreq4.imr_multiaddr.s_addr = htonl(groupAddress.toIPv4Address()); + + if (interface.isValid()) { + QList<QNetworkAddressEntry> addressEntries = interface.addressEntries(); + if (!addressEntries.isEmpty()) { + QHostAddress firstIP = addressEntries.first().ip(); + mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address()); + } else { + d->setError(QAbstractSocket::NetworkError, + QNativeSocketEnginePrivate::NetworkUnreachableErrorString); + return false; + } + } else { + mreq4.imr_interface.s_addr = INADDR_ANY; + } + } else { + // unreachable + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::ProtocolUnsupportedErrorString); + return false; + } + + int res = setsockopt(d->socketDescriptor, level, sockOpt, sockArg, sockArgSize); + if (res == -1) { + switch (errno) { + case ENOPROTOOPT: + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + break; + case EADDRNOTAVAIL: + d->setError(QAbstractSocket::SocketAddressNotAvailableError, + QNativeSocketEnginePrivate::AddressNotAvailableErrorString); + break; + default: + d->setError(QAbstractSocket::UnknownSocketError, + QNativeSocketEnginePrivate::UnknownSocketErrorString); + break; + } + return false; + } + return true; +} + +bool QNativeSocketEnginePrivate::nativeJoinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_JOIN_GROUP, +#else + 0, +#endif + IP_ADD_MEMBERSHIP, + groupAddress, + interface); +} + +bool QNativeSocketEnginePrivate::nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_LEAVE_GROUP, +#else + 0, +#endif + IP_DROP_MEMBERSHIP, + groupAddress, + interface); +} + +QNetworkInterface QNativeSocketEnginePrivate::nativeMulticastInterface() const +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, &v, &sizeofv) == -1) + return QNetworkInterface(); + return QNetworkInterface::interfaceFromIndex(v); + } +#endif + + struct in_addr v = { 0 }; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, &sizeofv) == -1) + return QNetworkInterface(); + if (v.s_addr != 0 && sizeofv >= sizeof(v)) { + QHostAddress ipv4(ntohl(v.s_addr)); + QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < ifaces.count(); ++i) { + const QNetworkInterface &iface = ifaces.at(i); + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int j = 0; j < entries.count(); ++j) { + const QNetworkAddressEntry &entry = entries.at(j); + if (entry.ip() == ipv4) + return iface; + } + } + } + return QNetworkInterface(); +} + +bool QNativeSocketEnginePrivate::nativeSetMulticastInterface(const QNetworkInterface &iface) +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v = iface.index(); + return (::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, &v, sizeof(v)) != -1); + } +#endif + + struct in_addr v; + if (iface.isValid()) { + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int i = 0; i < entries.count(); ++i) { + const QNetworkAddressEntry &entry = entries.at(i); + const QHostAddress &ip = entry.ip(); + if (ip.protocol() == QAbstractSocket::IPv4Protocol) { + v.s_addr = htonl(ip.toIPv4Address()); + int r = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, sizeof(v)); + if (r != -1) + return true; + } + } + return false; + } + + v.s_addr = INADDR_ANY; + return (::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, &v, sizeof(v)) != -1); +} + +#endif // QT_NO_NETWORKINTERFACE + +qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const +{ + int nbytes = 0; + // gives shorter than true amounts on Unix domain sockets. + qint64 available = 0; + if (qt_safe_ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) nbytes; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBytesAvailable() == %lli", available); +#endif + return available; +} + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ + // Create a sockaddr struct and reset its port number. + qt_sockaddr storage; + QT_SOCKLEN_T storageSize = sizeof(storage); + memset(&storage, 0, storageSize); + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we can't check recvfrom's return value. + ssize_t readBytes; + do { + char c; + readBytes = ::recvfrom(socketDescriptor, &c, 1, MSG_PEEK, &storage.a, &storageSize); + } while (readBytes == -1 && errno == EINTR); + + // If there's no error, or if our buffer was too small, there must be a + // pending datagram. + bool result = (readBytes != -1) || errno == EMSGSIZE; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", + result ? "true" : "false"); +#endif + return result; +} + +qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const +{ + QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); + ssize_t recvResult = -1; + + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + recvResult = ::recv(socketDescriptor, udpMessagePeekBuffer.data(), + udpMessagePeekBuffer.size(), MSG_PEEK); + if (recvResult == -1 && errno == EINTR) + continue; + + if (recvResult != (ssize_t) udpMessagePeekBuffer.size()) + break; + + udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %i", recvResult); +#endif + + return qint64(recvResult); +} + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxSize, + QHostAddress *address, quint16 *port) +{ + qt_sockaddr aa; + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + + ssize_t recvFromResult = 0; + do { + char c; + recvFromResult = ::recvfrom(socketDescriptor, maxSize ? data : &c, maxSize ? maxSize : 1, + 0, &aa.a, &sz); + } while (recvFromResult == -1 && errno == EINTR); + + if (recvFromResult == -1) { + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + } else if (port || address) { + qt_socket_getPortAndAddress(&aa, port, address); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %lli, %s, %i) == %lli", + data, qt_prettyDebug(data, qMin(recvFromResult, ssize_t(16)), recvFromResult).data(), maxSize, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, (qint64) recvFromResult); +#endif + + return qint64(maxSize ? recvFromResult : recvFromResult == -1 ? -1 : 0); +} + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &host, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); + + Q_IPV6ADDR tmp = host.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv6; + } else +#endif + if (host.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(host.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv4; + } + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + ssize_t sentBytes = qt_safe_sendto(socketDescriptor, data, len, + 0, sockAddrPtr, sockAddrSize); + + if (sentBytes < 0) { + switch (errno) { + case EMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::sendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data, + qt_prettyDebug(data, qMin<int>(len, 16), len).data(), len, host.toString().toLatin1().constData(), + port, (qint64) sentBytes); +#endif + + return qint64(sentBytes); +} + +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + + qt_sockaddr sa; + QT_SOCKLEN_T sockAddrSize = sizeof(sa); + + // Determine local address + memset(&sa, 0, sizeof(sa)); + if (::getsockname(socketDescriptor, &sa.a, &sockAddrSize) == 0) { + qt_socket_getPortAndAddress(&sa, &localPort, &localAddress); + + // Determine protocol family + switch (sa.a.sa_family) { + case AF_INET: + socketProtocol = QAbstractSocket::IPv4Protocol; + break; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + + } else if (errno == EBADF) { + setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString); + return false; + } + + // Determine the remote address + if (!::getpeername(socketDescriptor, &sa.a, &sockAddrSize)) + qt_socket_getPortAndAddress(&sa, &peerPort, &peerAddress); + + // Determine the socket type (UDP/TCP) + int value = 0; + QT_SOCKOPTLEN_T valueSize = sizeof(int); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, &value, &valueSize) == 0) { + if (value == SOCK_STREAM) + socketType = QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + socketType = QAbstractSocket::UdpSocket; + else + socketType = QAbstractSocket::UnknownSocketType; + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," + " peer == %s:%i, socket == %s - %s", + localAddress.toString().toLatin1().constData(), localPort, + peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(), + socketProtocolStr.toLatin1().constData()); +#endif + return true; +} + +void QNativeSocketEnginePrivate::nativeClose() +{ +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::nativeClose()"); +#endif + + qt_safe_close(socketDescriptor); +} + +qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QNativeSocketEngine); + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + + ssize_t writtenBytes; + writtenBytes = qt_safe_write(socketDescriptor, data, len); + + if (writtenBytes < 0) { + switch (errno) { + case EPIPE: + case ECONNRESET: + writtenBytes = -1; + setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString); + q->close(); + break; + case EAGAIN: + writtenBytes = 0; + break; + case EMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin((int) len, 16), + (int) len).data(), len, (int) writtenBytes); +#endif + + return qint64(writtenBytes); +} +/* +*/ +qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxSize) +{ + Q_Q(QNativeSocketEngine); + if (!q->isValid()) { + qWarning("QNativeSocketEngine::nativeRead: Invalid socket"); + return -1; + } + + ssize_t r = 0; + r = qt_safe_read(socketDescriptor, data, maxSize); + + if (r < 0) { + r = -1; + switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + // No data was available for reading + r = -2; + break; + case EBADF: + case EINVAL: + case EIO: + //error string is now set in read(), not here in nativeRead() + break; + case ECONNRESET: +#if defined(Q_OS_VXWORKS) + case ESHUTDOWN: +#endif + r = 0; + break; + default: + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin(r, ssize_t(16)), r).data(), + maxSize, r); +#endif + + return qint64(r); +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(socketDescriptor, &fds); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int retval; + if (selectForRead) + retval = qt_safe_select(socketDescriptor + 1, &fds, 0, 0, timeout < 0 ? 0 : &tv); + else + retval = qt_safe_select(socketDescriptor + 1, 0, &fds, 0, timeout < 0 ? 0 : &tv); + + return retval; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + fd_set fdread; + FD_ZERO(&fdread); + if (checkRead) + FD_SET(socketDescriptor, &fdread); + + fd_set fdwrite; + FD_ZERO(&fdwrite); + if (checkWrite) + FD_SET(socketDescriptor, &fdwrite); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret; + ret = qt_safe_select(socketDescriptor + 1, &fdread, &fdwrite, 0, timeout < 0 ? 0 : &tv); + + if (ret <= 0) + return ret; + *selectForRead = FD_ISSET(socketDescriptor, &fdread); + *selectForWrite = FD_ISSET(socketDescriptor, &fdwrite); + + return ret; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp new file mode 100644 index 0000000000..940569aa7b --- /dev/null +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -0,0 +1,1439 @@ +/**************************************************************************** +** +** 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 <winsock2.h> +#include <ws2tcpip.h> + +#include "qnativesocketengine_p.h" + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qdebug.h> +#include <qdatetime.h> +#include <qnetworkinterface.h> + +//#define QNATIVESOCKETENGINE_DEBUG +#if defined(QNATIVESOCKETENGINE_DEBUG) +# include <qstring.h> +# include <qbytearray.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(QNATIVESOCKETENGINE_DEBUG) + +void verboseWSErrorDebug(int r) +{ + switch (r) { + case WSANOTINITIALISED : qDebug("WSA error : WSANOTINITIALISED"); break; + case WSAEINTR: qDebug("WSA error : WSAEINTR"); break; + case WSAEBADF: qDebug("WSA error : WSAEBADF"); break; + case WSAEACCES: qDebug("WSA error : WSAEACCES"); break; + case WSAEFAULT: qDebug("WSA error : WSAEFAULT"); break; + case WSAEINVAL: qDebug("WSA error : WSAEINVAL"); break; + case WSAEMFILE: qDebug("WSA error : WSAEMFILE"); break; + case WSAEWOULDBLOCK: qDebug("WSA error : WSAEWOULDBLOCK"); break; + case WSAEINPROGRESS: qDebug("WSA error : WSAEINPROGRESS"); break; + case WSAEALREADY: qDebug("WSA error : WSAEALREADY"); break; + case WSAENOTSOCK: qDebug("WSA error : WSAENOTSOCK"); break; + case WSAEDESTADDRREQ: qDebug("WSA error : WSAEDESTADDRREQ"); break; + case WSAEMSGSIZE: qDebug("WSA error : WSAEMSGSIZE"); break; + case WSAEPROTOTYPE: qDebug("WSA error : WSAEPROTOTYPE"); break; + case WSAENOPROTOOPT: qDebug("WSA error : WSAENOPROTOOPT"); break; + case WSAEPROTONOSUPPORT: qDebug("WSA error : WSAEPROTONOSUPPORT"); break; + case WSAESOCKTNOSUPPORT: qDebug("WSA error : WSAESOCKTNOSUPPORT"); break; + case WSAEOPNOTSUPP: qDebug("WSA error : WSAEOPNOTSUPP"); break; + case WSAEPFNOSUPPORT: qDebug("WSA error : WSAEPFNOSUPPORT"); break; + case WSAEAFNOSUPPORT: qDebug("WSA error : WSAEAFNOSUPPORT"); break; + case WSAEADDRINUSE: qDebug("WSA error : WSAEADDRINUSE"); break; + case WSAEADDRNOTAVAIL: qDebug("WSA error : WSAEADDRNOTAVAIL"); break; + case WSAENETDOWN: qDebug("WSA error : WSAENETDOWN"); break; + case WSAENETUNREACH: qDebug("WSA error : WSAENETUNREACH"); break; + case WSAENETRESET: qDebug("WSA error : WSAENETRESET"); break; + case WSAECONNABORTED: qDebug("WSA error : WSAECONNABORTED"); break; + case WSAECONNRESET: qDebug("WSA error : WSAECONNRESET"); break; + case WSAENOBUFS: qDebug("WSA error : WSAENOBUFS"); break; + case WSAEISCONN: qDebug("WSA error : WSAEISCONN"); break; + case WSAENOTCONN: qDebug("WSA error : WSAENOTCONN"); break; + case WSAESHUTDOWN: qDebug("WSA error : WSAESHUTDOWN"); break; + case WSAETOOMANYREFS: qDebug("WSA error : WSAETOOMANYREFS"); break; + case WSAETIMEDOUT: qDebug("WSA error : WSAETIMEDOUT"); break; + case WSAECONNREFUSED: qDebug("WSA error : WSAECONNREFUSED"); break; + case WSAELOOP: qDebug("WSA error : WSAELOOP"); break; + case WSAENAMETOOLONG: qDebug("WSA error : WSAENAMETOOLONG"); break; + case WSAEHOSTDOWN: qDebug("WSA error : WSAEHOSTDOWN"); break; + case WSAEHOSTUNREACH: qDebug("WSA error : WSAEHOSTUNREACH"); break; + case WSAENOTEMPTY: qDebug("WSA error : WSAENOTEMPTY"); break; + case WSAEPROCLIM: qDebug("WSA error : WSAEPROCLIM"); break; + case WSAEUSERS: qDebug("WSA error : WSAEUSERS"); break; + case WSAEDQUOT: qDebug("WSA error : WSAEDQUOT"); break; + case WSAESTALE: qDebug("WSA error : WSAESTALE"); break; + case WSAEREMOTE: qDebug("WSA error : WSAEREMOTE"); break; + case WSAEDISCON: qDebug("WSA error : WSAEDISCON"); break; + default: qDebug("WSA error : Unknown"); break; + } + qErrnoWarning(r, "more details"); +} + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1().constData(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} + + +#define WS_ERROR_DEBUG(x) verboseWSErrorDebug(x); + +#else + +#define WS_ERROR_DEBUG(x) Q_UNUSED(x) + +#endif + +#ifndef AF_INET6 +#define AF_INET6 23 /* Internetwork Version 6 */ +#endif + +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) /* disallow local address reuse */ +#endif + +//### +#define QT_SOCKLEN_T int +#define QT_SOCKOPTLEN_T int + + +/* + Extracts the port and address from a sockaddr, and stores them in + \a port and \a addr if they are non-null. +*/ +static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, const qt_sockaddr *sa, quint16 *port, QHostAddress *address) +{ +#if !defined (QT_NO_IPV6) + if (sa->a.sa_family == AF_INET6) { + const qt_sockaddr_in6 *sa6 = &sa->a6; + Q_IPV6ADDR tmp; + for (int i = 0; i < 16; ++i) + tmp.c[i] = sa6->sin6_addr.qt_s6_addr[i]; + QHostAddress a; + a.setAddress(tmp); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa6->sin6_port, port); + } else +#endif + if (sa->a.sa_family == AF_INET) { + const sockaddr_in *sa4 = &sa->a4; + unsigned long addr; + WSANtohl(socketDescriptor, sa4->sin_addr.s_addr, &addr); + QHostAddress a; + a.setAddress(addr); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa4->sin_port, port); + } +} + + +/*! \internal + + Sets the port and address to a sockaddr. Requires that sa point to the IPv6 struct if the address is IPv6. +*/ +static inline void qt_socket_setPortAndAddress(SOCKET socketDescriptor, sockaddr_in * sockAddrIPv4, qt_sockaddr_in6 * sockAddrIPv6, + quint16 port, const QHostAddress & address, sockaddr ** sockAddrPtr, QT_SOCKLEN_T *sockAddrSize) +{ +#if !defined(QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(sockAddrIPv6, 0, sizeof(qt_sockaddr_in6)); + sockAddrIPv6->sin6_family = AF_INET6; + sockAddrIPv6->sin6_scope_id = address.scopeId().toInt(); + WSAHtons(socketDescriptor, port, &(sockAddrIPv6->sin6_port)); + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&(sockAddrIPv6->sin6_addr.qt_s6_addr), &tmp, sizeof(tmp)); + *sockAddrSize = sizeof(qt_sockaddr_in6); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol + || address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) { + memset(sockAddrIPv4, 0, sizeof(sockaddr_in)); + sockAddrIPv4->sin_family = AF_INET; + WSAHtons(socketDescriptor, port, &(sockAddrIPv4->sin_port)); + WSAHtonl(socketDescriptor, address.toIPv4Address(), &(sockAddrIPv4->sin_addr.s_addr)); + *sockAddrSize = sizeof(sockaddr_in); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv4; + } else { + // unreachable + } +} + +/*! \internal + +*/ +static inline QAbstractSocket::SocketType qt_socket_getType(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } else { + if (value == SOCK_STREAM) + return QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + return QAbstractSocket::UdpSocket; + } + return QAbstractSocket::UnknownSocketType; +} + +/*! \internal + +*/ +static inline int qt_socket_getMaxMsgSize(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } + return value; +} + +QWindowsSockInit::QWindowsSockInit() +: version(0) +{ + //### should we try for 2.2 on all platforms ?? + WSAData wsadata; + + // IPv6 requires Winsock v2.0 or better. + if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) { + qWarning("QTcpSocketAPI: WinSock v2.0 initialization failed."); + } else { + version = 0x20; + } +} + +QWindowsSockInit::~QWindowsSockInit() +{ + WSACleanup(); +} + +// MS Transport Provider IOCTL to control +// reporting PORT_UNREACHABLE messages +// on UDP sockets via recv/WSARecv/etc. +// Path TRUE in input buffer to enable (default if supported), +// FALSE to disable. +#ifndef SIO_UDP_CONNRESET +# ifndef IOC_VENDOR +# define IOC_VENDOR 0x18000000 +# endif +# ifndef _WSAIOW +# define _WSAIOW(x,y) (IOC_IN|(x)|(y)) +# endif +# define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) +#endif + +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ + + //### no ip6 support on winsocket 1.1 but we will try not to use this !!!!!!!!!!!!1 + /* + if (winsockVersion < 0x20 && socketProtocol == QAbstractSocket::IPv6Protocol) { + //### no ip6 support + return -1; + } + */ + + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + // MSDN KB179942 states that on winnt 4 WSA_FLAG_OVERLAPPED is needed if socket is to be non blocking + // and recomends alwasy doing it for cross windows version comapablity. + SOCKET socket = ::WSASocket(protocol, type, 0, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (socket == INVALID_SOCKET) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEAFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEPROTOTYPE: + case WSAEINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString); + break; + case WSAEMFILE: + case WSAENOBUFS: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + default: + break; + } + + return false; + } + +#if !defined(Q_OS_WINCE) + if (socketType == QAbstractSocket::UdpSocket) { + // enable new behavior using + // SIO_UDP_CONNRESET + DWORD dwBytesReturned = 0; + int bNewBehavior = 1; + if (::WSAIoctl(socket, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), + NULL, 0, &dwBytesReturned, NULL, NULL) == SOCKET_ERROR) { + // not to worry isBogusUdpReadNotification() should handle this otherwise + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } + } +#endif + + socketDescriptor = socket; + return true; + +} + +/*! \internal + + Returns the value of the socket option \a opt. +*/ +int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) const +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return -1; + + int n = -1; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: { + unsigned long buf = 0; + if (WSAIoctl(socketDescriptor, FIONBIO, 0,0, &buf, sizeof(buf), 0,0,0) == 0) + return buf; + else + return -1; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + break; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + case QNativeSocketEngine::LowDelayOption: + level = IPPROTO_TCP; + n = TCP_NODELAY; + break; + case QNativeSocketEngine::KeepAliveOption: + n = SO_KEEPALIVE; + break; + case QNativeSocketEngine::MulticastTtlOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (getsockopt(socketDescriptor, level, n, (char *) &v, &len) != -1) + return v; + return -1; +} + + +/*! \internal + Sets the socket option \a opt to \a v. +*/ +bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v) +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return false; + + int n = 0; + int level = SOL_SOCKET; // default + + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + { + unsigned long buf = v; + unsigned long outBuf; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONBIO, &buf, sizeof(unsigned long), &outBuf, sizeof(unsigned long), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + break; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + case QNativeSocketEngine::LowDelayOption: + level = IPPROTO_TCP; + n = TCP_NODELAY; + break; + case QNativeSocketEngine::KeepAliveOption: + n = SO_KEEPALIVE; + break; + case QNativeSocketEngine::MulticastTtlOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_HOPS; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_TTL; + } + break; + case QNativeSocketEngine::MulticastLoopbackOption: +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + n = IPV6_MULTICAST_LOOP; + } else +#endif + { + level = IPPROTO_IP; + n = IP_MULTICAST_LOOP; + } + break; + } + + if (::setsockopt(socketDescriptor, level, n, (char*)&v, sizeof(v)) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; +} + +/*! + Fetches information about both ends of the connection: whatever is + available. +*/ +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + + qt_sockaddr sa; + QT_SOCKLEN_T sockAddrSize = sizeof(sa); + + // Determine local address + memset(&sa, 0, sizeof(sa)); + if (::getsockname(socketDescriptor, &sa.a, &sockAddrSize) == 0) { + qt_socket_getPortAndAddress(socketDescriptor, &sa, &localPort, &localAddress); + // Determine protocol family + switch (sa.a.sa_family) { + case AF_INET: + socketProtocol = QAbstractSocket::IPv4Protocol; + break; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + } else { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + if (err == WSAENOTSOCK) { + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidSocketErrorString); + return false; + } + } + + memset(&sa, 0, sizeof(sa)); + if (::getpeername(socketDescriptor, &sa.a, &sockAddrSize) == 0) { + qt_socket_getPortAndAddress(socketDescriptor, &sa, &peerPort, &peerAddress); + } else { + WS_ERROR_DEBUG(WSAGetLastError()); + } + + socketType = qt_socket_getType(socketDescriptor); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() localAddress == %s, localPort = %i, peerAddress == %s, peerPort = %i, socketProtocol == %s, socketType == %s", localAddress.toString().toLatin1().constData(), localPort, peerAddress.toString().toLatin1().constData(), peerPort, socketProtocolStr.toLatin1().constData(), socketTypeStr.toLatin1().constData()); +#endif + + return true; +} + + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quint16 port) +{ + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect() to %s :: %i", address.toString().toLatin1().constData(), port); +#endif + + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + forever { + int connectResult = ::WSAConnect(socketDescriptor, sockAddrPtr, sockAddrSize, 0,0,0,0); + if (connectResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEISCONN: + socketState = QAbstractSocket::ConnectedState; + break; + case WSAEWOULDBLOCK: { + // If WSAConnect returns WSAEWOULDBLOCK on the second + // connection attempt, we have to check SO_ERROR's + // value to detect ECONNREFUSED. If we don't get + // ECONNREFUSED, we'll have to treat it as an + // unfinished operation. + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAETIMEDOUT) { + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAEHOSTUNREACH) { + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAEADDRNOTAVAIL) { + setError(QAbstractSocket::NetworkError, AddressNotAvailableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + } + // fall through + } + case WSAEINPROGRESS: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + socketState = QAbstractSocket::ConnectingState; + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::NetworkError, AddressInuseErrorString); + break; + case WSAECONNREFUSED: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAETIMEDOUT: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEHOSTUNREACH: + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAENETUNREACH: + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEINVAL: + case WSAEALREADY: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + break; + default: + break; + } + if (socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, + socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + + socketState = QAbstractSocket::ConnectedState; + return true; +} + + +bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &a, quint16 port) +{ + QHostAddress address = a; + switch (address.protocol()) { + case QAbstractSocket::IPv6Protocol: + if (address.toIPv6Address()[0] == 0xff) { + // binding to a multicast address + address = QHostAddress(QHostAddress::AnyIPv6); + } + break; + case QAbstractSocket::IPv4Protocol: + if ((address.toIPv4Address() & 0xffff0000) == 0xefff0000) { + // binding to a multicast address + address = QHostAddress(QHostAddress::Any); + } + break; + default: + break; + } + + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + + int bindResult = ::bind(socketDescriptor, sockAddrPtr, sockAddrSize); + if (bindResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + case WSAEINVAL: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString); + break; + case WSAEADDRNOTAVAIL: + setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + socketState = QAbstractSocket::BoundState; + return true; +} + + +bool QNativeSocketEnginePrivate::nativeListen(int backlog) +{ + if (::listen(socketDescriptor, backlog) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = WSAAccept(socketDescriptor, 0,0,0,0); + if (acceptedDescriptor != -1 && QAbstractEventDispatcher::instance()) { + // Because of WSAAsyncSelect() WSAAccept returns a non blocking socket + // with the same attributes as the listening socket including the current + // WSAAsyncSelect(). To be able to change the socket to blocking mode the + // WSAAsyncSelect() call must be cancled. + QSocketNotifier n(acceptedDescriptor, QSocketNotifier::Read); + n.setEnabled(true); + n.setEnabled(false); + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeAccept() == %i", acceptedDescriptor); +#endif + return acceptedDescriptor; +} + +static bool multicastMembershipHelper(QNativeSocketEnginePrivate *d, + int how6, + int how4, + const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + int level = 0; + int sockOpt = 0; + char *sockArg; + int sockArgSize; + + struct ip_mreq mreq4; +#ifndef QT_NO_IPV6 + struct ipv6_mreq mreq6; + + if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) { + level = IPPROTO_IPV6; + sockOpt = how6; + sockArg = reinterpret_cast<char *>(&mreq6); + sockArgSize = sizeof(mreq6); + memset(&mreq6, 0, sizeof(mreq6)); + Q_IPV6ADDR ip6 = groupAddress.toIPv6Address(); + memcpy(&mreq6.ipv6mr_multiaddr, &ip6, sizeof(ip6)); + mreq6.ipv6mr_interface = iface.index(); + } else +#endif + if (groupAddress.protocol() == QAbstractSocket::IPv4Protocol) { + level = IPPROTO_IP; + sockOpt = how4; + sockArg = reinterpret_cast<char *>(&mreq4); + sockArgSize = sizeof(mreq4); + memset(&mreq4, 0, sizeof(mreq4)); + mreq4.imr_multiaddr.s_addr = htonl(groupAddress.toIPv4Address()); + + if (iface.isValid()) { + QList<QNetworkAddressEntry> addressEntries = iface.addressEntries(); + if (!addressEntries.isEmpty()) { + QHostAddress firstIP = addressEntries.first().ip(); + mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address()); + } else { + d->setError(QAbstractSocket::NetworkError, + QNativeSocketEnginePrivate::NetworkUnreachableErrorString); + return false; + } + } else { + mreq4.imr_interface.s_addr = INADDR_ANY; + } + } else { + // unreachable + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::ProtocolUnsupportedErrorString); + return false; + } + + int res = setsockopt(d->socketDescriptor, level, sockOpt, sockArg, sockArgSize); + if (res == -1) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + return false; + } + return true; +} + +bool QNativeSocketEnginePrivate::nativeJoinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_JOIN_GROUP, +#else + 0, +#endif + IP_ADD_MEMBERSHIP, + groupAddress, + iface); +} + +bool QNativeSocketEnginePrivate::nativeLeaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + return multicastMembershipHelper(this, +#ifndef QT_NO_IPV6 + IPV6_LEAVE_GROUP, +#else + 0, +#endif + IP_DROP_MEMBERSHIP, + groupAddress, + iface); +} + +QNetworkInterface QNativeSocketEnginePrivate::nativeMulticastInterface() const +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *) &v, &sizeofv) == -1) + return QNetworkInterface(); + return QNetworkInterface::interfaceFromIndex(v); + } +#endif + + struct in_addr v; + v.s_addr = 0; + QT_SOCKOPTLEN_T sizeofv = sizeof(v); + if (::getsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, (char *) &v, &sizeofv) == -1) + return QNetworkInterface(); + if (v.s_addr != 0 && sizeofv >= QT_SOCKOPTLEN_T(sizeof(v))) { + QHostAddress ipv4(ntohl(v.s_addr)); + QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < ifaces.count(); ++i) { + const QNetworkInterface &iface = ifaces.at(i); + if (!(iface.flags() & QNetworkInterface::CanMulticast)) + continue; + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int j = 0; j < entries.count(); ++j) { + const QNetworkAddressEntry &entry = entries.at(j); + if (entry.ip() == ipv4) + return iface; + } + } + } + return QNetworkInterface(); +} + +bool QNativeSocketEnginePrivate::nativeSetMulticastInterface(const QNetworkInterface &iface) +{ +#ifndef QT_NO_IPV6 + if (socketProtocol == QAbstractSocket::IPv6Protocol) { + uint v = iface.isValid() ? iface.index() : 0; + return (::setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *) &v, sizeof(v)) != -1); + } +#endif + + struct in_addr v; + if (iface.isValid()) { + QList<QNetworkAddressEntry> entries = iface.addressEntries(); + for (int i = 0; i < entries.count(); ++i) { + const QNetworkAddressEntry &entry = entries.at(i); + const QHostAddress &ip = entry.ip(); + if (ip.protocol() == QAbstractSocket::IPv4Protocol) { + v.s_addr = htonl(ip.toIPv4Address()); + int r = ::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, (char *) &v, sizeof(v)); + if (r != -1) + return true; + } + } + return false; + } + + v.s_addr = INADDR_ANY; + return (::setsockopt(socketDescriptor, IPPROTO_IP, IP_MULTICAST_IF, (char *) &v, sizeof(v)) != -1); +} + +qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const +{ + unsigned long nbytes = 0; + unsigned long dummy = 0; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONREAD, &dummy, sizeof(dummy), &nbytes, sizeof(nbytes), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return -1; + } + + // ioctlsocket sometimes reports 1 byte available for datagrams + // while the following recvfrom returns -1 and claims connection + // was reset (udp is connectionless). so we peek one byte to + // catch this case and return 0 bytes available if recvfrom + // fails. + if (nbytes == 1 && socketType == QAbstractSocket::UdpSocket) { + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD flags = MSG_PEEK; + if (::WSARecvFrom(socketDescriptor, &buf, 1, 0, &flags, 0,0,0,0) == SOCKET_ERROR) + return 0; + } + return nbytes; +} + + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ +#if !defined(Q_OS_WINCE) + // Create a sockaddr struct and reset its port number. + qt_sockaddr storage; + QT_SOCKLEN_T storageSize = sizeof(storage); + memset(&storage, 0, storageSize); + + bool result = false; + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we check if there was a sender. + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD available = 0; + DWORD flags = MSG_PEEK; + int ret = ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, &storage.a, &storageSize,0,0); + int err = WSAGetLastError(); + if (ret == SOCKET_ERROR && err != WSAEMSGSIZE) { + WS_ERROR_DEBUG(err); + if (err == WSAECONNRESET) { + // Discard error message to prevent QAbstractSocket from + // getting this message repeatedly after reenabling the + // notifiers. + flags = 0; + ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, + &storage.a, &storageSize, 0, 0); + } + } else { + // If there's no error, or if our buffer was too small, there must be + // a pending datagram. + result = true; + } + +#else // Q_OS_WINCE + bool result = false; + fd_set readS; + FD_ZERO(&readS); + FD_SET((SOCKET)socketDescriptor, &readS); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 5000; + int available = ::select(1, &readS, 0, 0, &timeout); + result = available > 0 ? true : false; +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", + result ? "true" : "false"); +#endif + return result; +} + + +qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const +{ + qint64 ret = -1; +#if !defined(Q_OS_WINCE) + int recvResult = 0; + DWORD flags; + DWORD bufferCount = 5; + WSABUF * buf = 0; + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + static char udpMessagePeekBuffer[8192]; + + buf = new WSABUF[bufferCount]; + for (DWORD i=0; i<bufferCount; i++) { + buf[i].buf = udpMessagePeekBuffer; + buf[i].len = sizeof(udpMessagePeekBuffer); + } + flags = MSG_PEEK; + DWORD bytesRead = 0; + recvResult = ::WSARecv(socketDescriptor, buf, bufferCount, &bytesRead, &flags, 0,0); + int err = WSAGetLastError(); + if (recvResult != SOCKET_ERROR) { + ret = qint64(bytesRead); + break; + } else if (recvResult == SOCKET_ERROR && err == WSAEMSGSIZE) { + bufferCount += 5; + delete[] buf; + } else if (recvResult == SOCKET_ERROR) { + WS_ERROR_DEBUG(err); + ret = -1; + break; + } + } + + if (buf) + delete[] buf; + +#else // Q_OS_WINCE + DWORD size = -1; + DWORD bytesReturned; + int ioResult = WSAIoctl(socketDescriptor, FIONREAD, 0,0, &size, sizeof(size), &bytesReturned, 0, 0); + if (ioResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } else { + ret = qint64(size); + } +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %li", ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port) +{ + qint64 ret = 0; + + qt_sockaddr aa; + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + + WSABUF buf; + buf.buf = data; + buf.len = maxLength; +#if !defined(Q_OS_WINCE) + buf.buf = data; + buf.len = maxLength; +#else + char tmpChar; + buf.buf = data ? data : &tmpChar; + buf.len = maxLength; +#endif + + DWORD flags = 0; + DWORD bytesRead = 0; + int wsaRet = ::WSARecvFrom(socketDescriptor, &buf, 1, &bytesRead, &flags, &aa.a, &sz,0,0); + if (wsaRet == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEMSGSIZE) { + // it is ok the buffer was to small if bytesRead is larger than + // maxLength then assume bytes read is really maxLenth + ret = qint64(bytesRead) > maxLength ? maxLength : qint64(bytesRead); + } else { + WS_ERROR_DEBUG(err); + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + ret = -1; + } + } else { + ret = qint64(bytesRead); + } + + qt_socket_getPortAndAddress(socketDescriptor, &aa, port, address); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %li, %s, %i) == %li", + data, qt_prettyDebug(data, qMin<qint64>(ret, 16), ret).data(), maxLength, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &address, quint16 port) +{ + qint64 ret = -1; + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + WSABUF buf; +#if !defined(Q_OS_WINCE) + buf.buf = len ? (char*)data : 0; +#else + char tmp; + buf.buf = len ? (char*)data : &tmp; +#endif + buf.len = len; + DWORD flags = 0; + DWORD bytesSent = 0; + if (::WSASendTo(socketDescriptor, &buf, 1, &bytesSent, flags, sockAddrPtr, sockAddrSize, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + break; + } + ret = -1; + } else { + ret = qint64(bytesSent); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeSendDatagram(%p \"%s\", %li, \"%s\", %i) == %li", data, + qt_prettyDebug(data, qMin<qint64>(len, 16), len).data(), 0, address.toString().toLatin1().constData(), + port, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QNativeSocketEngine); + qint64 ret = 0; + qint64 bytesToSend = len; + + for (;;) { + WSABUF buf; + buf.buf = (char*)data + ret; + buf.len = bytesToSend; + DWORD flags = 0; + DWORD bytesWritten = 0; + + int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0); + + ret += qint64(bytesWritten); + + int err; + if (socketRet != SOCKET_ERROR) { + if (ret == len) + break; + else + continue; + } else if ((err = WSAGetLastError()) == WSAEWOULDBLOCK) { + break; + } else if (err == WSAENOBUFS) { + // this function used to not send more than 49152 per call to WSASendTo + // to avoid getting a WSAENOBUFS. However this is a performance regression + // and we think it only appears with old windows versions. We now handle the + // WSAENOBUFS and hope it never appears anyway. + // just go on, the next loop run we will try a smaller number + } else { + WS_ERROR_DEBUG(err); + switch (err) { + case WSAECONNRESET: + case WSAECONNABORTED: + ret = -1; + setError(QAbstractSocket::NetworkError, WriteErrorString); + q->close(); + break; + default: + break; + } + break; + } + + // for next send: + bytesToSend = qMin<qint64>(49152, len - ret); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %li) == %li", + data, qt_prettyDebug(data, qMin((int)ret, 16), (int)ret).data(), (int)len, (int)ret); +#endif + + return ret; +} + +qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) +{ + qint64 ret = -1; + WSABUF buf; + buf.buf = data; + buf.len = maxLength; + DWORD flags = 0; + DWORD bytesRead = 0; +#if defined(Q_OS_WINCE) + WSASetLastError(0); +#endif + if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEWOULDBLOCK: + ret = -2; + break; + case WSAEBADF: + case WSAEINVAL: + //error string is now set in read(), not here in nativeRead() + break; + case WSAECONNRESET: + case WSAECONNABORTED: + // for tcp sockets this will be handled in QNativeSocketEngine::read + ret = 0; + break; + default: + break; + } + } else { + if (WSAGetLastError() == WSAEWOULDBLOCK) + ret = -2; + else + ret = qint64(bytesRead); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + if (ret != -2) { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %l) == %li", + data, qt_prettyDebug(data, qMin((int)bytesRead, 16), (int)bytesRead).data(), (int)maxLength, (int)ret); + } else { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p, %l) == -2 (WOULD BLOCK)", + data, int(maxLength)); + } +#endif + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + bool readEnabled = selectForRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fds; + + int ret = 0; + + memset(&fds, 0, sizeof(fd_set)); + fds.fd_count = 1; + fds.fd_array[0] = (SOCKET)socketDescriptor; + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + if (selectForRead) { + ret = select(0, &fds, 0, 0, timeout < 0 ? 0 : &tv); + } else { + // select for write + + // Windows needs this to report errors when connecting a socket ... + fd_set fdexception; + FD_ZERO(&fdexception); + FD_SET((SOCKET)socketDescriptor, &fdexception); + + ret = select(0, 0, &fds, &fdexception, timeout < 0 ? 0 : &tv); + + // ... but if it is actually set, pretend it did not happen + if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception)) + ret--; + } + + if (readEnabled) + readNotifier->setEnabled(true); + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, + bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + bool readEnabled = checkRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fdread; + fd_set fdwrite; + fd_set fdexception; + + int ret = 0; + + memset(&fdread, 0, sizeof(fd_set)); + if (checkRead) { + fdread.fd_count = 1; + fdread.fd_array[0] = (SOCKET)socketDescriptor; + } + memset(&fdwrite, 0, sizeof(fd_set)); + FD_ZERO(&fdexception); + if (checkWrite) { + fdwrite.fd_count = 1; + fdwrite.fd_array[0] = (SOCKET)socketDescriptor; + + // Windows needs this to report errors when connecting a socket + FD_SET((SOCKET)socketDescriptor, &fdexception); + } + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + +#if !defined(Q_OS_WINCE) + ret = select(socketDescriptor + 1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); +#else + ret = select(1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv); +#endif + + //... but if it is actually set, pretend it did not happen + if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception)) + ret--; + + if (readEnabled) + readNotifier->setEnabled(true); + + if (ret <= 0) + return ret; + + *selectForRead = FD_ISSET((SOCKET)socketDescriptor, &fdread); + *selectForWrite = FD_ISSET((SOCKET)socketDescriptor, &fdwrite); + + return ret; +} + +void QNativeSocketEnginePrivate::nativeClose() +{ +#if defined (QTCPSOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeClose()"); +#endif + // We were doing a setsockopt here before with SO_DONTLINGER. (However with kind of wrong + // usage of parameters, it wants a BOOL but we used a struct and pretended it to be bool). + // We don't think setting this option should be done here, if a user wants it she/he can + // do it manually with socketDescriptor()/setSocketDescriptor(); + ::closesocket(socketDescriptor); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnet_unix_p.h b/src/network/socket/qnet_unix_p.h new file mode 100644 index 0000000000..c406ed948c --- /dev/null +++ b/src/network/socket/qnet_unix_p.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** 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 QNET_UNIX_P_H +#define QNET_UNIX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt code on Unix. This header file may change from version to +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qcore_unix_p.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#if defined(Q_OS_VXWORKS) +# include <sockLib.h> +#endif + +// for inet_addr +#include <netdb.h> +#include <arpa/inet.h> +#if defined(Q_OS_VXWORKS) +# include <hostLib.h> +#else +# include <resolv.h> +#endif + +QT_BEGIN_NAMESPACE + +// Almost always the same. If not, specify in qplatformdefs.h. +#if !defined(QT_SOCKOPTLEN_T) +# define QT_SOCKOPTLEN_T QT_SOCKLEN_T +#endif + +// UnixWare 7 redefines socket -> _socket +static inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0) +{ + Q_ASSERT((flags & ~O_NONBLOCK) == 0); + + register int fd; +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + int newtype = type | SOCK_CLOEXEC; + if (flags & O_NONBLOCK) + newtype |= SOCK_NONBLOCK; + fd = ::socket(domain, newtype, protocol); + if (fd != -1 || errno != EINVAL) + return fd; +#endif + + fd = ::socket(domain, type, protocol); + if (fd == -1) + return -1; + + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + + // set non-block too? + if (flags & O_NONBLOCK) + ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK); + + return fd; +} + +// Tru64 redefines accept -> _accept with _XOPEN_SOURCE_EXTENDED +static inline int qt_safe_accept(int s, struct sockaddr *addr, QT_SOCKLEN_T *addrlen, int flags = 0) +{ + Q_ASSERT((flags & ~O_NONBLOCK) == 0); + + register int fd; +#if QT_UNIX_SUPPORTS_THREADSAFE_CLOEXEC && defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + // use accept4 + int sockflags = SOCK_CLOEXEC; + if (flags & O_NONBLOCK) + sockflags |= SOCK_NONBLOCK; + fd = ::accept4(s, addr, static_cast<QT_SOCKLEN_T *>(addrlen), sockflags); + if (fd != -1 || !(errno == ENOSYS || errno == EINVAL)) + return fd; +#endif + + fd = ::accept(s, addr, static_cast<QT_SOCKLEN_T *>(addrlen)); + if (fd == -1) + return -1; + + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + + // set non-block too? + if (flags & O_NONBLOCK) + ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK); + + return fd; +} + +// UnixWare 7 redefines listen -> _listen +static inline int qt_safe_listen(int s, int backlog) +{ + return ::listen(s, backlog); +} + +static inline int qt_safe_connect(int sockfd, const struct sockaddr *addr, QT_SOCKLEN_T addrlen) +{ + register int ret; + // Solaris e.g. expects a non-const 2nd parameter + EINTR_LOOP(ret, QT_SOCKET_CONNECT(sockfd, const_cast<struct sockaddr *>(addr), addrlen)); + return ret; +} +#undef QT_SOCKET_CONNECT +#define QT_SOCKET_CONNECT qt_safe_connect + +#if defined(socket) +# undef socket +#endif +#if defined(accept) +# undef accept +#endif +#if defined(listen) +# undef listen +#endif + +// VxWorks' headers specify 'int' instead of '...' for the 3rd ioctl() parameter. +template <typename T> +static inline int qt_safe_ioctl(int sockfd, int request, T arg) +{ +#ifdef Q_OS_VXWORKS + return ::ioctl(sockfd, request, (int) arg); +#else + return ::ioctl(sockfd, request, arg); +#endif +} + +// VxWorks' headers do not specify any const modifiers +static inline in_addr_t qt_safe_inet_addr(const char *cp) +{ +#ifdef Q_OS_VXWORKS + return ::inet_addr((char *) cp); +#else + return ::inet_addr(cp); +#endif +} + +// VxWorks' headers do not specify any const modifiers +static inline int qt_safe_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *to, QT_SOCKLEN_T tolen) +{ +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + register int ret; +#ifdef Q_OS_VXWORKS + EINTR_LOOP(ret, ::sendto(sockfd, (char *) buf, len, flags, (struct sockaddr *) to, tolen)); +#else + EINTR_LOOP(ret, ::sendto(sockfd, buf, len, flags, to, tolen)); +#endif + return ret; +} + +QT_END_NAMESPACE + +#endif // QNET_UNIX_P_H diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp new file mode 100644 index 0000000000..c365635990 --- /dev/null +++ b/src/network/socket/qsocks5socketengine.cpp @@ -0,0 +1,1923 @@ +/**************************************************************************** +** +** 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 "qsocks5socketengine_p.h" + +#ifndef QT_NO_SOCKS5 + +#include "qtcpsocket.h" +#include "qudpsocket.h" +#include "qtcpserver.h" +#include "qdebug.h" +#include "qhash.h" +#include "qqueue.h" +#include "qelapsedtimer.h" +#include "qmutex.h" +#include "qthread.h" +#include "qcoreapplication.h" +#include "qurl.h" +#include "qauthenticator.h" +#include <qendian.h> +#include <qnetworkinterface.h> + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_SYMBIAN +static const int MaxWriteBufferSize = 4*1024; +#else +static const int MaxWriteBufferSize = 128*1024; +#endif + +//#define QSOCKS5SOCKETLAYER_DEBUG + +#define MAX_DATA_DUMP 256 +#if !defined(Q_OS_WINCE) +#define SOCKS5_BLOCKING_BIND_TIMEOUT 5000 +#else +#define SOCKS5_BLOCKING_BIND_TIMEOUT 10000 +#endif + +#define Q_INIT_CHECK(returnValue) do { \ + if (!d->data) { \ + return returnValue; \ + } } while (0) + +#define S5_VERSION_5 0x05 +#define S5_CONNECT 0x01 +#define S5_BIND 0x02 +#define S5_UDP_ASSOCIATE 0x03 +#define S5_IP_V4 0x01 +#define S5_DOMAINNAME 0x03 +#define S5_IP_V6 0x04 +#define S5_SUCCESS 0x00 +#define S5_R_ERROR_SOCKS_FAILURE 0x01 +#define S5_R_ERROR_CON_NOT_ALLOWED 0x02 +#define S5_R_ERROR_NET_UNREACH 0x03 +#define S5_R_ERROR_HOST_UNREACH 0x04 +#define S5_R_ERROR_CONN_REFUSED 0x05 +#define S5_R_ERROR_TTL 0x06 +#define S5_R_ERROR_CMD_NOT_SUPPORTED 0x07 +#define S5_R_ERROR_ADD_TYPE_NOT_SUPORTED 0x08 + +#define S5_AUTHMETHOD_NONE 0x00 +#define S5_AUTHMETHOD_PASSWORD 0x02 +#define S5_AUTHMETHOD_NOTACCEPTABLE 0xFF + +#define S5_PASSWORDAUTH_VERSION 0x01 + +#ifdef QSOCKS5SOCKETLAYER_DEBUG +# define QSOCKS5_Q_DEBUG qDebug() << this +# define QSOCKS5_D_DEBUG qDebug() << q_ptr +# define QSOCKS5_DEBUG qDebug() << "[QSocks5]" +static QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State s) +{ + switch (s) { + case QSocks5SocketEnginePrivate::Uninitialized: return QLatin1String("Uninitialized"); + case QSocks5SocketEnginePrivate::ConnectError: return QLatin1String("ConnectError"); + case QSocks5SocketEnginePrivate::AuthenticationMethodsSent: return QLatin1String("AuthenticationMethodsSent"); + case QSocks5SocketEnginePrivate::Authenticating: return QLatin1String("Authenticating"); + case QSocks5SocketEnginePrivate::AuthenticatingError: return QLatin1String("AuthenticatingError"); + case QSocks5SocketEnginePrivate::RequestMethodSent: return QLatin1String("RequestMethodSent"); + case QSocks5SocketEnginePrivate::RequestError: return QLatin1String("RequestError"); + case QSocks5SocketEnginePrivate::Connected: return QLatin1String("Connected"); + case QSocks5SocketEnginePrivate::UdpAssociateSuccess: return QLatin1String("UdpAssociateSuccess"); + case QSocks5SocketEnginePrivate::BindSuccess: return QLatin1String("BindSuccess"); + case QSocks5SocketEnginePrivate::ControlSocketError: return QLatin1String("ControlSocketError"); + case QSocks5SocketEnginePrivate::SocksError: return QLatin1String("SocksError"); + case QSocks5SocketEnginePrivate::HostNameLookupError: return QLatin1String("HostNameLookupError"); + default: break; + } + return QLatin1String("unknown state"); +} + +static QString dump(const QByteArray &buf) +{ + QString data; + for (int i = 0; i < qMin<int>(MAX_DATA_DUMP, buf.size()); ++i) { + if (i) data += QLatin1Char(' '); + uint val = (unsigned char)buf.at(i); + // data += QString("0x%1").arg(val, 3, 16, QLatin1Char('0')); + data += QString::number(val); + } + if (buf.size() > MAX_DATA_DUMP) + data += QLatin1String(" ..."); + + return QString::fromLatin1("size: %1 data: { %2 }").arg(buf.size()).arg(data); +} + +#else +# define QSOCKS5_DEBUG if (0) qDebug() +# define QSOCKS5_Q_DEBUG if (0) qDebug() +# define QSOCKS5_D_DEBUG if (0) qDebug() + +static inline QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State) { return QString(); } +static inline QString dump(const QByteArray &) { return QString(); } +#endif + +/* + inserts the host address in buf at pos and updates pos. + if the func fails the data in buf and the vallue of pos is undefined +*/ +static bool qt_socks5_set_host_address_and_port(const QHostAddress &address, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << address << ':' << port << ']'; + + union { + quint16 port; + quint32 ipv4; + QIPv6Address ipv6; + char ptr; + } data; + + // add address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + data.ipv4 = qToBigEndian<quint32>(address.toIPv4Address()); + pBuf->append(S5_IP_V4); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv4)); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + data.ipv6 = address.toIPv6Address(); + pBuf->append(S5_IP_V6); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv6)); + } else { + return false; + } + + // add port + data.port = qToBigEndian<quint16>(port); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + return true; +} + +/* + like above, but for a hostname +*/ +static bool qt_socks5_set_host_name_and_port(const QString &hostname, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << hostname << ':' << port << ']'; + + QByteArray encodedHostName = QUrl::toAce(hostname); + QByteArray &buf = *pBuf; + + if (encodedHostName.length() > 255) + return false; + + buf.append(S5_DOMAINNAME); + buf.append(uchar(encodedHostName.length())); + buf.append(encodedHostName); + + // add port + union { + quint16 port; + char ptr; + } data; + data.port = qToBigEndian<quint16>(port); + buf.append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + + return true; +} + + +/* + retrives the host address in buf at pos and updates pos. + if the func fails the value of the address and the pos is undefined +*/ +static bool qt_socks5_get_host_address_and_port(const QByteArray &buf, QHostAddress *pAddress, quint16 *pPort, int *pPos) +{ + bool ret = false; + int pos = *pPos; + const unsigned char *pBuf = reinterpret_cast<const unsigned char*>(buf.constData()); + QHostAddress address; + quint16 port = 0; + + if (buf.size() - pos < 1) { + QSOCKS5_DEBUG << "need more data address/port"; + return false; + } + if (pBuf[pos] == S5_IP_V4) { + pos++; + if (buf.size() - pos < 4) { + QSOCKS5_DEBUG << "need more data for ip4 address"; + return false; + } + address.setAddress(qFromBigEndian<quint32>(&pBuf[pos])); + pos += 4; + ret = true; + } else if (pBuf[pos] == S5_IP_V6) { + pos++; + if (buf.size() - pos < 16) { + QSOCKS5_DEBUG << "need more data for ip6 address"; + return false; + } + QIPv6Address add; + for (int i = 0; i < 16; ++i) + add[i] = buf[pos++]; + ret = true; + } else if (pBuf[pos] == S5_DOMAINNAME){ + // just skip it + pos++; + qDebug() << "skipping hostname of len" << uint(pBuf[pos]); + pos += uchar(pBuf[pos]); + } else { + QSOCKS5_DEBUG << "invalid address type" << (int)pBuf[pos]; + ret = false; + } + + if (ret) { + if (buf.size() - pos < 2) { + QSOCKS5_DEBUG << "need more data for port"; + return false; + } + port = qFromBigEndian<quint16>(&pBuf[pos]); + pos += 2; + } + + if (ret) { + QSOCKS5_DEBUG << "got [" << address << ':' << port << ']'; + *pAddress = address; + *pPort = port; + *pPos = pos; + } + + return ret; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +struct QSocks5Data +{ + QTcpSocket *controlSocket; + QSocks5Authenticator *authenticator; +}; + +struct QSocks5ConnectData : public QSocks5Data +{ + QByteArray readBuffer; +}; + +struct QSocks5BindData : public QSocks5Data +{ + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QElapsedTimer timeStamp; +}; + +struct QSocks5RevivedDatagram +{ + QByteArray data; + QHostAddress address; + quint16 port; +}; + +#ifndef QT_NO_UDPSOCKET +struct QSocks5UdpAssociateData : public QSocks5Data +{ + QUdpSocket *udpSocket; + QHostAddress associateAddress; + quint16 associatePort; + QQueue<QSocks5RevivedDatagram> pendingDatagrams; +}; +#endif + +// needs to be thread safe +class QSocks5BindStore : public QObject +{ +public: + QSocks5BindStore(); + ~QSocks5BindStore(); + + void add(int socketDescriptor, QSocks5BindData *bindData); + bool contains(int socketDescriptor); + QSocks5BindData *retrieve(int socketDescriptor); + +protected: + void timerEvent(QTimerEvent * event); + + QMutex mutex; + int sweepTimerId; + //socket descriptor, data, timestamp + QHash<int, QSocks5BindData *> store; +}; + +Q_GLOBAL_STATIC(QSocks5BindStore, socks5BindStore) + +QSocks5BindStore::QSocks5BindStore() + : mutex(QMutex::Recursive) + , sweepTimerId(-1) +{ + QCoreApplication *app = QCoreApplication::instance(); + if (app && app->thread() != thread()) + moveToThread(app->thread()); +} + +QSocks5BindStore::~QSocks5BindStore() +{ +} + +void QSocks5BindStore::add(int socketDescriptor, QSocks5BindData *bindData) +{ + QMutexLocker lock(&mutex); + if (store.contains(socketDescriptor)) { + // qDebug() << "delete it"; + } + bindData->timeStamp.start(); + store.insert(socketDescriptor, bindData); + // start sweep timer if not started + if (sweepTimerId == -1) + sweepTimerId = startTimer(60000); +} + +bool QSocks5BindStore::contains(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + return store.contains(socketDescriptor); +} + +QSocks5BindData *QSocks5BindStore::retrieve(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + if (!store.contains(socketDescriptor)) + return 0; + QSocks5BindData *bindData = store.take(socketDescriptor); + if (bindData) { + if (bindData->controlSocket->thread() != QThread::currentThread()) { + qWarning("Can not access socks5 bind data from different thread"); + return 0; + } + } else { + QSOCKS5_DEBUG << "__ERROR__ binddata == 0"; + } + // stop the sweep timer if not needed + if (store.isEmpty()) { + killTimer(sweepTimerId); + sweepTimerId = -1; + } + return bindData; +} + +void QSocks5BindStore::timerEvent(QTimerEvent * event) +{ + QMutexLocker lock(&mutex); + if (event->timerId() == sweepTimerId) { + QSOCKS5_DEBUG << "QSocks5BindStore performing sweep"; + QMutableHashIterator<int, QSocks5BindData *> it(store); + while (it.hasNext()) { + it.next(); + if (it.value()->timeStamp.hasExpired(350000)) { + QSOCKS5_DEBUG << "QSocks5BindStore removing JJJJ"; + it.remove(); + } + } + } +} + +QSocks5Authenticator::QSocks5Authenticator() +{ +} + +QSocks5Authenticator::~QSocks5Authenticator() +{ +} + +char QSocks5Authenticator::methodId() +{ + return 0x00; +} + +bool QSocks5Authenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::seal(const QByteArray buf, QByteArray *sealedBuf) +{ + *sealedBuf = buf; + return true; +} + +bool QSocks5Authenticator::unSeal(const QByteArray sealedBuf, QByteArray *buf) +{ + *buf = sealedBuf; + return true; +} + +bool QSocks5Authenticator::unSeal(QTcpSocket *sealedSocket, QByteArray *buf) +{ + return unSeal(sealedSocket->readAll(), buf); +} + +QSocks5PasswordAuthenticator::QSocks5PasswordAuthenticator(const QString &userName, const QString &password) +{ + this->userName = userName; + this->password = password; +} + +char QSocks5PasswordAuthenticator::methodId() +{ + return 0x02; +} + +bool QSocks5PasswordAuthenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + QByteArray uname = userName.toLatin1(); + QByteArray passwd = password.toLatin1(); + QByteArray dataBuf(3 + uname.size() + passwd.size(), 0); + char *buf = dataBuf.data(); + int pos = 0; + buf[pos++] = S5_PASSWORDAUTH_VERSION; + buf[pos++] = uname.size(); + memcpy(&buf[pos], uname.data(), uname.size()); + pos += uname.size(); + buf[pos++] = passwd.size(); + memcpy(&buf[pos], passwd.data(), passwd.size()); + return socket->write(dataBuf) == dataBuf.size(); +} + +bool QSocks5PasswordAuthenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + + if (socket->bytesAvailable() < 2) + return true; + + QByteArray buf = socket->read(2); + if (buf.at(0) == S5_PASSWORDAUTH_VERSION && buf.at(1) == 0x00) { + *completed = true; + return true; + } + + // must disconnect + socket->close(); + return false; +} + +QString QSocks5PasswordAuthenticator::errorString() +{ + return QLatin1String("Socks5 user name or password incorrect"); +} + + + +QSocks5SocketEnginePrivate::QSocks5SocketEnginePrivate() + : socks5State(Uninitialized) + , readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , socketDescriptor(-1) + , data(0) + , connectData(0) +#ifndef QT_NO_UDPSOCKET + , udpData(0) +#endif + , bindData(0) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) +{ + mode = NoMode; +} + +QSocks5SocketEnginePrivate::~QSocks5SocketEnginePrivate() +{ +} + +void QSocks5SocketEnginePrivate::initialize(Socks5Mode socks5Mode) +{ + Q_Q(QSocks5SocketEngine); + + mode = socks5Mode; + if (mode == ConnectMode) { + connectData = new QSocks5ConnectData; + data = connectData; +#ifndef QT_NO_UDPSOCKET + } else if (mode == UdpAssociateMode) { + udpData = new QSocks5UdpAssociateData; + data = udpData; + udpData->udpSocket = new QUdpSocket(q); +#ifndef QT_NO_BEARERMANAGEMENT + udpData->udpSocket->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + udpData->udpSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(udpData->udpSocket, SIGNAL(readyRead()), + q, SLOT(_q_udpSocketReadNotification()), + Qt::DirectConnection); +#endif // QT_NO_UDPSOCKET + } else if (mode == BindMode) { + bindData = new QSocks5BindData; + data = bindData; + } + + data->controlSocket = new QTcpSocket(q); +#ifndef QT_NO_BEARERMANAGEMENT + data->controlSocket->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + data->controlSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(data->controlSocket, SIGNAL(connected()), q, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(readyRead()), q, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(bytesWritten(qint64)), q, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(disconnected()), q, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + if (!proxyInfo.user().isEmpty() || !proxyInfo.password().isEmpty()) { + QSOCKS5_D_DEBUG << "using username/password authentication; user =" << proxyInfo.user(); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + } else { + QSOCKS5_D_DEBUG << "not using authentication"; + data->authenticator = new QSocks5Authenticator(); + } +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, const QString &extraMessage) +{ + Q_Q(QSocks5SocketEngine); + + switch (state) { + case Uninitialized: + case Authenticating: + case AuthenticationMethodsSent: + case RequestMethodSent: + case Connected: + case UdpAssociateSuccess: + case BindSuccess: + // these aren't error states + return; + + case ConnectError: + case ControlSocketError: { + QAbstractSocket::SocketError controlSocketError = data->controlSocket->error(); + if (socks5State != Connected) { + switch (controlSocketError) { + case QAbstractSocket::ConnectionRefusedError: + q->setError(QAbstractSocket::ProxyConnectionRefusedError, + QSocks5SocketEngine::tr("Connection to proxy refused")); + break; + case QAbstractSocket::RemoteHostClosedError: + q->setError(QAbstractSocket::ProxyConnectionClosedError, + QSocks5SocketEngine::tr("Connection to proxy closed prematurely")); + break; + case QAbstractSocket::HostNotFoundError: + q->setError(QAbstractSocket::ProxyNotFoundError, + QSocks5SocketEngine::tr("Proxy host not found")); + break; + case QAbstractSocket::SocketTimeoutError: + if (state == ConnectError) { + q->setError(QAbstractSocket::ProxyConnectionTimeoutError, + QSocks5SocketEngine::tr("Connection to proxy timed out")); + break; + } + /* fall through */ + default: + q->setError(controlSocketError, data->controlSocket->errorString()); + break; + } + } else { + q->setError(controlSocketError, data->controlSocket->errorString()); + } + break; + } + + case AuthenticatingError: + q->setError(QAbstractSocket::ProxyAuthenticationRequiredError, + extraMessage.isEmpty() ? + QSocks5SocketEngine::tr("Proxy authentication failed") : + QSocks5SocketEngine::tr("Proxy authentication failed: %1").arg(extraMessage)); + break; + + case RequestError: + // error code set by caller (overload) + break; + + case SocksError: + q->setError(QAbstractSocket::ProxyProtocolError, + QSocks5SocketEngine::tr("SOCKS version 5 protocol error")); + break; + + case HostNameLookupError: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + } + + q->setState(QAbstractSocket::UnconnectedState); + socks5State = state; +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, Socks5Error socks5error) +{ + Q_Q(QSocks5SocketEngine); + switch (socks5error) { + case SocksFailure: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("General SOCKSv5 server failure")); + break; + case ConnectionNotAllowed: + q->setError(QAbstractSocket::SocketAccessError, + QSocks5SocketEngine::tr("Connection not allowed by SOCKSv5 server")); + break; + case NetworkUnreachable: + q->setError(QAbstractSocket::NetworkError, + QAbstractSocket::tr("Network unreachable")); + break; + case HostUnreachable: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + case ConnectionRefused: + q->setError(QAbstractSocket::ConnectionRefusedError, + QAbstractSocket::tr("Connection refused")); + break; + case TTLExpired: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("TTL expired")); + break; + case CommandNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("SOCKSv5 command not supported")); + break; + case AddressTypeNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("Address type not supported")); + break; + + default: + q->setError(QAbstractSocket::UnknownSocketError, + QSocks5SocketEngine::tr("Unknown SOCKSv5 proxy error code 0x%1").arg(int(socks5error), 16)); + break; + } + + setErrorState(state, QString()); +} + +void QSocks5SocketEnginePrivate::reauthenticate() +{ + Q_Q(QSocks5SocketEngine); + + // we require authentication + QAuthenticator auth; + emit q->proxyAuthenticationRequired(proxyInfo, &auth); + + if (!auth.user().isEmpty() || !auth.password().isEmpty()) { + // we have new credentials, let's try again + QSOCKS5_DEBUG << "authentication failure: retrying connection"; + socks5State = QSocks5SocketEnginePrivate::Uninitialized; + + delete data->authenticator; + proxyInfo.setUser(auth.user()); + proxyInfo.setPassword(auth.password()); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + + data->controlSocket->blockSignals(true); + data->controlSocket->abort(); + data->controlSocket->blockSignals(false); + data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port()); + } else { + // authentication failure + + setErrorState(AuthenticatingError); + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::parseAuthenticationMethodReply() +{ + // not enough data to begin + if (data->controlSocket->bytesAvailable() < 2) + return; + + QByteArray buf = data->controlSocket->read(2); + if (buf.at(0) != S5_VERSION_5) { + QSOCKS5_D_DEBUG << "Socks5 version incorrect"; + setErrorState(SocksError); + data->controlSocket->close(); + emitConnectionNotification(); + return; + } + + bool authComplete = false; + if (uchar(buf.at(1)) == S5_AUTHMETHOD_NONE) { + authComplete = true; + } else if (uchar(buf.at(1)) == S5_AUTHMETHOD_NOTACCEPTABLE) { + reauthenticate(); + return; + } else if (buf.at(1) != data->authenticator->methodId() + || !data->authenticator->beginAuthenticate(data->controlSocket, &authComplete)) { + setErrorState(AuthenticatingError, QLatin1String("Socks5 host did not support authentication method.")); + socketError = QAbstractSocket::SocketAccessError; // change the socket error + emitConnectionNotification(); + return; + } + + if (authComplete) + sendRequestMethod(); + else + socks5State = Authenticating; +} + +void QSocks5SocketEnginePrivate::parseAuthenticatingReply() +{ + bool authComplete = false; + if (!data->authenticator->continueAuthenticate(data->controlSocket, &authComplete)) { + reauthenticate(); + return; + } + if (authComplete) + sendRequestMethod(); +} + +void QSocks5SocketEnginePrivate::sendRequestMethod() +{ + QHostAddress address; + quint16 port = 0; + char command = 0; + if (mode == ConnectMode) { + command = S5_CONNECT; + address = peerAddress; + port = peerPort; + } else if (mode == BindMode) { + command = S5_BIND; + address = localAddress; + port = localPort; + } else { +#ifndef QT_NO_UDPSOCKET + command = S5_UDP_ASSOCIATE; + address = localAddress; //data->controlSocket->localAddress(); + port = localPort; +#endif + } + + QByteArray buf; + buf.reserve(270); // big enough for domain name; + buf[0] = S5_VERSION_5; + buf[1] = command; + buf[2] = 0x00; + if (peerName.isEmpty() && !qt_socks5_set_host_address_and_port(address, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } else if (!peerName.isEmpty() && !qt_socks5_set_host_name_and_port(peerName, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } + QSOCKS5_DEBUG << "sending" << dump(buf); + QByteArray sealedBuf; + if (!data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + data->controlSocket->write(sealedBuf); + data->controlSocket->flush(); + socks5State = RequestMethodSent; +} + +void QSocks5SocketEnginePrivate::parseRequestMethodReply() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_DEBUG << "parseRequestMethodReply()"; + + QByteArray inBuf; + if (!data->authenticator->unSeal(data->controlSocket, &inBuf)) { + // ### check error and not just not enough data + QSOCKS5_DEBUG << "unSeal failed, needs more data"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + if (inBuf.size() < 2) { + QSOCKS5_DEBUG << "need more data for request reply header .. put this data somewhere"; + return; + } + + QHostAddress address; + quint16 port = 0; + + if (inBuf.at(0) != S5_VERSION_5 || inBuf.length() < 3 || inBuf.at(2) != 0x00) { + QSOCKS5_DEBUG << "socks protocol error"; + setErrorState(SocksError); + } else if (inBuf.at(1) != S5_SUCCESS) { + Socks5Error socks5Error = Socks5Error(inBuf.at(1)); + QSOCKS5_DEBUG << "Request error :" << socks5Error; + if ((socks5Error == SocksFailure || socks5Error == ConnectionNotAllowed) + && !peerName.isEmpty()) { + // Dante seems to use this error code to indicate hostname resolution failure + setErrorState(HostNameLookupError); + } else { + setErrorState(RequestError, socks5Error); + } + } else { + // connection success, retrieve the remote addresses + int pos = 3; + if (!qt_socks5_get_host_address_and_port(inBuf, &address, &port, &pos)) { + QSOCKS5_DEBUG << "error getting address"; + setErrorState(SocksError); + } else { + inBuf.remove(0, pos); + for (int i = inBuf.size() - 1; i >= 0 ; --i) + data->controlSocket->ungetChar(inBuf.at(i)); + } + } + + if (socks5State == RequestMethodSent) { + // no error + localAddress = address; + localPort = port; + + if (mode == ConnectMode) { + socks5State = Connected; + // notify the upper layer that we're done + q->setState(QAbstractSocket::ConnectedState); + emitConnectionNotification(); + } else if (mode == BindMode) { + socks5State = BindSuccess; + q->setState(QAbstractSocket::ListeningState); + } else { + socks5State = UdpAssociateSuccess; + } + } else if (socks5State == BindSuccess) { + // no error and we got a connection + bindData->peerAddress = address; + bindData->peerPort = port; + + emitReadNotification(); + } else { + // got an error + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationPending = false; + if (readNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting readNotification"; + QPointer<QSocks5SocketEngine> qq = q; + emit q->readNotification(); + if (!qq) + return; + // check if there needs to be a new zero read notification + if (data && data->controlSocket->state() == QAbstractSocket::UnconnectedState + && data->controlSocket->error() == QAbstractSocket::RemoteHostClosedError) { + connectData->readBuffer.clear(); + emitReadNotification(); + } + } +} + +void QSocks5SocketEnginePrivate::emitReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationActivated = true; + if (readNotificationEnabled && !readNotificationPending) { + QSOCKS5_D_DEBUG << "queueing readNotification"; + readNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingWriteNotification() +{ + writeNotificationPending = false; + Q_Q(QSocks5SocketEngine); + if (writeNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting writeNotification"; + emit q->writeNotification(); + } +} + +void QSocks5SocketEnginePrivate::emitWriteNotification() +{ + Q_Q(QSocks5SocketEngine); + writeNotificationActivated = true; + if (writeNotificationEnabled && !writeNotificationPending) { + QSOCKS5_D_DEBUG << "queueing writeNotification"; + writeNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingConnectionNotification() +{ + connectionNotificationPending = false; + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "emitting connectionNotification"; + emit q->connectionNotification(); +} + +void QSocks5SocketEnginePrivate::emitConnectionNotification() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "queueing connectionNotification"; + connectionNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingConnectionNotification", Qt::QueuedConnection); +} + +QSocks5SocketEngine::QSocks5SocketEngine(QObject *parent) +:QAbstractSocketEngine(*new QSocks5SocketEnginePrivate(), parent) +{ +} + +QSocks5SocketEngine::~QSocks5SocketEngine() +{ + Q_D(QSocks5SocketEngine); + + if (d->data) { + delete d->data->authenticator; + delete d->data->controlSocket; + } + if (d->connectData) + delete d->connectData; +#ifndef QT_NO_UDPSOCKET + if (d->udpData) { + delete d->udpData->udpSocket; + delete d->udpData; + } +#endif + if (d->bindData) + delete d->bindData; +} + +static QBasicAtomicInt descriptorCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +bool QSocks5SocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QSocks5SocketEngine); + + d->socketDescriptor = descriptorCounter.fetchAndAddRelaxed(1); + + d->socketType = type; + d->socketProtocol = protocol; + + return true; +} + +bool QSocks5SocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "initialize" << socketDescriptor; + + // this is only valid for the other side of a bind, nothing else is supported + + if (socketState != QAbstractSocket::ConnectedState) { + //### must be connected state ??? + return false; + } + + QSocks5BindData *bindData = socks5BindStore()->retrieve(socketDescriptor); + if (bindData) { + + d->socketState = QAbstractSocket::ConnectedState; + d->socketType = QAbstractSocket::TcpSocket; + d->connectData = new QSocks5ConnectData; + d->data = d->connectData; + d->mode = QSocks5SocketEnginePrivate::ConnectMode; + d->data->controlSocket = bindData->controlSocket; + bindData->controlSocket = 0; + d->data->controlSocket->setParent(this); + d->socketProtocol = d->data->controlSocket->localAddress().protocol(); + d->data->authenticator = bindData->authenticator; + bindData->authenticator = 0; + d->localPort = bindData->localPort; + d->localAddress = bindData->localAddress; + d->peerPort = bindData->peerPort; + d->peerAddress = bindData->peerAddress; + delete bindData; + + QObject::connect(d->data->controlSocket, SIGNAL(connected()), this, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(readyRead()), this, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(disconnected()), this, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + d->socks5State = QSocks5SocketEnginePrivate::Connected; + + if (d->data->controlSocket->bytesAvailable() != 0) + d->_q_controlSocketReadNotification(); + return true; + } + return false; +} + +void QSocks5SocketEngine::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QSocks5SocketEngine); + d->proxyInfo = networkProxy; +} + +int QSocks5SocketEngine::socketDescriptor() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketDescriptor; +} + +bool QSocks5SocketEngine::isValid() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketType != QAbstractSocket::UnknownSocketType + && d->socks5State != QSocks5SocketEnginePrivate::SocksError + && (d->socketError == QAbstractSocket::UnknownSocketError + || d->socketError == QAbstractSocket::SocketTimeoutError + || d->socketError == QAbstractSocket::UnfinishedSocketOperationError); +} + +bool QSocks5SocketEngine::connectInternal() +{ + Q_D(QSocks5SocketEngine); + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::ConnectMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) + return false; + + setState(QAbstractSocket::ConnectedState); + return true; +#endif + } else { + qFatal("QSocks5SocketEngine::connectToHost: in QTcpServer mode"); + return false; + } + } + + if (d->socks5State == QSocks5SocketEnginePrivate::Uninitialized + && d->socketState != QAbstractSocket::ConnectingState) { + setState(QAbstractSocket::ConnectingState); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + return false; + } + return false; +} + +bool QSocks5SocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "connectToHost" << address << ':' << port; + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QSocks5SocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +void QSocks5SocketEnginePrivate::_q_controlSocketConnected() +{ + QSOCKS5_DEBUG << "_q_controlSocketConnected"; + QByteArray buf(3, 0); + buf[0] = S5_VERSION_5; + buf[1] = 0x01; + buf[2] = data->authenticator->methodId(); + data->controlSocket->write(buf); + socks5State = AuthenticationMethodsSent; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketReadNotification socks5state" << s5StateToString(socks5State) + << "bytes available" << data->controlSocket->bytesAvailable(); + + if (data->controlSocket->bytesAvailable() == 0) { + QSOCKS5_D_DEBUG << "########## bogus read why do we get these ... on windows only"; + return; + } + + switch (socks5State) { + case AuthenticationMethodsSent: + parseAuthenticationMethodReply(); + break; + case Authenticating: + parseAuthenticatingReply(); + break; + case RequestMethodSent: + parseRequestMethodReply(); + break; + case Connected: { + QByteArray buf; + if (!data->authenticator->unSeal(data->controlSocket, &buf)) { + // qDebug() << "unseal error maybe need to wait for more data"; + } + if (buf.size()) { + QSOCKS5_DEBUG << dump(buf); + connectData->readBuffer += buf; + emitReadNotification(); + } + break; + } + case BindSuccess: + // only get here if command is bind + if (mode == BindMode) { + parseRequestMethodReply(); + break; + } + + // fall through + default: + qWarning("QSocks5SocketEnginePrivate::_q_controlSocketReadNotification: " + "Unexpectedly received data while in state=%d and mode=%d", + socks5State, mode); + break; + }; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketBytesWritten() +{ + QSOCKS5_DEBUG << "_q_controlSocketBytesWritten"; + + if (socks5State != Connected + || (mode == ConnectMode + && data->controlSocket->bytesToWrite())) + return; + if (data->controlSocket->bytesToWrite() < MaxWriteBufferSize) { + emitWriteNotification(); + writeNotificationActivated = false; + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketError(QAbstractSocket::SocketError error) +{ + QSOCKS5_D_DEBUG << "controlSocketError" << error << data->controlSocket->errorString(); + + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error -- comes from the waitFor* functions + + if (error == QAbstractSocket::RemoteHostClosedError + && socks5State == Connected) { + // clear the read buffer in connect mode so that bytes available returns 0 + // if there already is a read notification pending then this will be processed first + if (!readNotificationPending) + connectData->readBuffer.clear(); + emitReadNotification(); + data->controlSocket->close(); + // cause a disconnect in the outer socket + emitWriteNotification(); + } else if (socks5State == Uninitialized + || socks5State == AuthenticationMethodsSent + || socks5State == Authenticating + || socks5State == RequestMethodSent) { + setErrorState(socks5State == Uninitialized ? ConnectError : ControlSocketError); + data->controlSocket->close(); + emitConnectionNotification(); + } else { + q_func()->setError(data->controlSocket->error(), data->controlSocket->errorString()); + emitReadNotification(); + emitWriteNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketDisconnected() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketDisconnected"; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketStateChanged(QAbstractSocket::SocketState state) +{ + QSOCKS5_D_DEBUG << "_q_controlSocketStateChanged" << state; +} + +#ifndef QT_NO_UDPSOCKET +void QSocks5SocketEnginePrivate::checkForDatagrams() const +{ + // udp should be unbuffered so we need to do some polling at certain points + if (udpData->udpSocket->hasPendingDatagrams()) + const_cast<QSocks5SocketEnginePrivate *>(this)->_q_udpSocketReadNotification(); +} + +void QSocks5SocketEnginePrivate::_q_udpSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_udpSocketReadNotification()"; + + // check some state stuff + if (!udpData->udpSocket->hasPendingDatagrams()) { + QSOCKS5_D_DEBUG << "false read ??"; + return; + } + + while (udpData->udpSocket->hasPendingDatagrams()) { + QByteArray sealedBuf(udpData->udpSocket->pendingDatagramSize(), 0); + QSOCKS5_D_DEBUG << "new datagram"; + udpData->udpSocket->readDatagram(sealedBuf.data(), sealedBuf.size()); + QByteArray inBuf; + if (!data->authenticator->unSeal(sealedBuf, &inBuf)) { + QSOCKS5_D_DEBUG << "failed unsealing datagram discarding"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + int pos = 0; + const char *buf = inBuf.constData(); + if (inBuf.size() < 4) { + QSOCKS5_D_DEBUG << "bugus udp data, discarding"; + return; + } + QSocks5RevivedDatagram datagram; + if (buf[pos++] != 0 || buf[pos++] != 0) { + QSOCKS5_D_DEBUG << "invalid datagram discarding"; + return; + } + if (buf[pos++] != 0) { //### add fragmentation reading support + QSOCKS5_D_DEBUG << "don't support fragmentation yet disgarding"; + return; + } + if (!qt_socks5_get_host_address_and_port(inBuf, &datagram.address, &datagram.port, &pos)) { + QSOCKS5_D_DEBUG << "failed to get address from datagram disgarding"; + return; + } + datagram.data = QByteArray(&buf[pos], inBuf.size() - pos); + udpData->pendingDatagrams.enqueue(datagram); + } + emitReadNotification(); +} +#endif // QT_NO_UDPSOCKET + +bool QSocks5SocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // when bind wee will block until the bind is finished as the info from the proxy server is needed + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::BindMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); +#endif + } else { + //### something invalid + return false; + } + } + +#ifndef QT_NO_UDPSOCKET + if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + if (!d->udpData->udpSocket->bind(address, port)) { + QSOCKS5_Q_DEBUG << "local udp bind failed"; + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + return false; + } + d->localAddress = d->udpData->udpSocket->localAddress(); + d->localPort = d->udpData->udpSocket->localPort(); + } else +#endif + if (d->mode == QSocks5SocketEnginePrivate::BindMode) { + d->localAddress = address; + d->localPort = port; + } else { + //### something invalid + return false; + } + + int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT; + QElapsedTimer stopWatch; + stopWatch.start(); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + if (!d->waitForConnected(msecs, 0) || + d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + // waitForConnected sets the error state and closes the socket + QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString(); + return false; + } + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + setState(QAbstractSocket::BoundState); + return true; +#ifndef QT_NO_UDPSOCKET + } else if (d->socks5State == QSocks5SocketEnginePrivate::UdpAssociateSuccess) { + setState(QAbstractSocket::BoundState); + d->udpData->associateAddress = d->localAddress; + d->localAddress = QHostAddress(); + d->udpData->associatePort = d->localPort; + d->localPort = 0; + QUdpSocket dummy; +#ifndef QT_NO_BEARERMANAGEMENT + dummy.setProperty("_q_networksession", property("_q_networksession")); +#endif + dummy.setProxy(QNetworkProxy::NoProxy); + if (!dummy.bind() + || writeDatagram(0,0, d->data->controlSocket->localAddress(), dummy.localPort()) != 0 + || !dummy.waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed())) + || dummy.readDatagram(0,0, &d->localAddress, &d->localPort) != 0) { + QSOCKS5_DEBUG << "udp actual address and port lookup failed"; + setState(QAbstractSocket::UnconnectedState); + setError(dummy.error(), dummy.errorString()); + d->data->controlSocket->close(); + //### reset and error + return false; + } + QSOCKS5_DEBUG << "udp actual address and port" << d->localAddress << ':' << d->localPort; + return true; +#endif // QT_NO_UDPSOCKET + } + + // binding timed out + setError(QAbstractSocket::SocketTimeoutError, + QLatin1String(QT_TRANSLATE_NOOP("QSocks5SocketEngine", "Network operation timed out"))); + +///### delete d->udpSocket; +///### d->udpSocket = 0; + return false; +} + + +bool QSocks5SocketEngine::listen() +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "listen()"; + + // check that we are in bound and then go to listening. + if (d->socketState == QAbstractSocket::BoundState) { + d->socketState = QAbstractSocket::ListeningState; + + // check if we already have a connection + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + d->emitReadNotification(); + + return true; + } + return false; +} + +int QSocks5SocketEngine::accept() +{ + Q_D(QSocks5SocketEngine); + // check we are listing --- + + QSOCKS5_Q_DEBUG << "accept()"; + + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + QSOCKS5_Q_DEBUG << "BindSuccess adding" << d->socketDescriptor << "to the bind store"; + d->data->controlSocket->disconnect(); + d->data->controlSocket->setParent(0); + d->bindData->localAddress = d->localAddress; + d->bindData->localPort = d->localPort; + int sd = d->socketDescriptor; + socks5BindStore()->add(sd, d->bindData); + d->data = 0; + d->bindData = 0; + d->socketDescriptor = 0; + //### do something about this socket layer ... set it closed and an error about why ... + // reset state and local port/address + d->socks5State = QSocks5SocketEnginePrivate::Uninitialized; // ..?? + d->socketState = QAbstractSocket::UnconnectedState; + return sd; + } + return -1; +} + +void QSocks5SocketEngine::close() +{ + QSOCKS5_Q_DEBUG << "close()"; + Q_D(QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) { + int msecs = 100; + QElapsedTimer stopWatch; + stopWatch.start(); + while (!d->data->controlSocket->bytesToWrite()) { + if (!d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed()))) + break; + } + } + d->data->controlSocket->close(); + } +#ifndef QT_NO_UDPSOCKET + if (d->udpData && d->udpData->udpSocket) + d->udpData->udpSocket->close(); +#endif +} + +qint64 QSocks5SocketEngine::bytesAvailable() const +{ + Q_D(const QSocks5SocketEngine); + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + return d->connectData->readBuffer.size(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode + && !d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.first().data.size(); +#endif + return 0; +} + +qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "read( , maxlen = " << maxlen << ')'; + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + if (d->connectData->readBuffer.size() == 0) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + //imitate remote closed + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed connection###")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } else { + return 0; // nothing to be read + } + } + qint64 copy = qMin<qint64>(d->connectData->readBuffer.size(), maxlen); + memcpy(data, d->connectData->readBuffer.constData(), copy); + d->connectData->readBuffer.remove(0, copy); + QSOCKS5_DEBUG << "read" << dump(QByteArray(data, copy)); + return copy; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + return readDatagram(data, maxlen); +#endif + } + return 0; +} + +qint64 QSocks5SocketEngine::write(const char *data, qint64 len) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "write" << dump(QByteArray(data, len)); + + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + // clamp down the amount of bytes to transfer at once + len = qMin<qint64>(len, MaxWriteBufferSize) - d->data->controlSocket->bytesToWrite(); + if (len <= 0) + return 0; + + QByteArray buf = QByteArray::fromRawData(data, len); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + + d->data->controlSocket->write(sealedBuf); + d->data->controlSocket->waitForBytesWritten(0); + return len; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + // send to connected address + return writeDatagram(data, len, d->peerAddress, d->peerPort); +#endif + } + //### set an error ??? + return -1; +} + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE +bool QSocks5SocketEngine::joinMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + +bool QSocks5SocketEngine::leaveMulticastGroup(const QHostAddress &, + const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} + + +QNetworkInterface QSocks5SocketEngine::multicastInterface() const +{ + return QNetworkInterface(); +} + +bool QSocks5SocketEngine::setMulticastInterface(const QNetworkInterface &) +{ + setError(QAbstractSocket::UnsupportedSocketOperationError, + QLatin1String("Operation on socket is not supported")); + return false; +} +#endif // QT_NO_NETWORKINTERFACE + +qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QHostAddress *addr, + quint16 *port) +{ + Q_D(QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (d->udpData->pendingDatagrams.isEmpty()) + return 0; + + QSocks5RevivedDatagram datagram = d->udpData->pendingDatagrams.dequeue(); + int copyLen = qMin<int>(maxlen, datagram.data.size()); + memcpy(data, datagram.data.constData(), copyLen); + if (addr) + *addr = datagram.address; + if (port) + *port = datagram.port; + return copyLen; +} + +qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QHostAddress &address, + quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // it is possible to send with out first binding with udp, but socks5 requires a bind. + if (!d->data) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) { + //### set error + return -1; + } + } + + QByteArray outBuf; + outBuf.reserve(270 + len); + outBuf[0] = 0x00; + outBuf[1] = 0x00; + outBuf[2] = 0x00; + if (!qt_socks5_set_host_address_and_port(address, port, &outBuf)) { + } + outBuf += QByteArray(data, len); + QSOCKS5_DEBUG << "sending" << dump(outBuf); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(outBuf, &sealedBuf)) { + QSOCKS5_DEBUG << "sealing data failed"; + setError(QAbstractSocket::SocketAccessError, d->data->authenticator->errorString()); + return -1; + } + if (d->udpData->udpSocket->writeDatagram(sealedBuf, d->udpData->associateAddress, d->udpData->associatePort) != sealedBuf.size()) { + //### try frgamenting + if (d->udpData->udpSocket->error() == QAbstractSocket::DatagramTooLargeError) + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + //### else maybe more serious error + return -1; + } + + return len; +} + +bool QSocks5SocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSocks5SocketEngine); + Q_INIT_CHECK(false); + + d->checkForDatagrams(); + + return !d->udpData->pendingDatagrams.isEmpty(); +} + +qint64 QSocks5SocketEngine::pendingDatagramSize() const +{ + Q_D(const QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (!d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.head().data.size(); + return 0; +} +#endif // QT_NO_UDPSOCKET + +qint64 QSocks5SocketEngine::bytesToWrite() const +{ + Q_D(const QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + return d->data->controlSocket->bytesToWrite(); + } else { + return 0; + } +} + +int QSocks5SocketEngine::option(SocketOption option) const +{ + Q_D(const QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + return d->data->controlSocket->socketOption(QAbstractSocket::LowDelayOption).toInt(); + if (option == QAbstractSocketEngine::KeepAliveOption) + return d->data->controlSocket->socketOption(QAbstractSocket::KeepAliveOption).toInt(); + } + return -1; +} + +bool QSocks5SocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + // convert the enum and call the real socket + if (option == QAbstractSocketEngine::LowDelayOption) + d->data->controlSocket->setSocketOption(QAbstractSocket::LowDelayOption, value); + if (option == QAbstractSocketEngine::KeepAliveOption) + d->data->controlSocket->setSocketOption(QAbstractSocket::KeepAliveOption, value); + return true; + } + return false; +} + +bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut) +{ + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return false; + + const Socks5State wantedState = + mode == ConnectMode ? Connected : + mode == BindMode ? BindSuccess : + UdpAssociateSuccess; + + QElapsedTimer stopWatch; + stopWatch.start(); + + while (socks5State != wantedState) { + if (!data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setErrorState(QSocks5SocketEnginePrivate::ControlSocketError); + if (timedOut && data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + return true; +} + +bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForRead" << msecs; + + d->readNotificationActivated = false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode || + d->mode == QSocks5SocketEnginePrivate::BindMode) { + while (!d->readNotificationActivated) { + if (!d->data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setError(d->data->controlSocket->error(), d->data->controlSocket->errorString()); + if (timedOut && d->data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#ifndef QT_NO_UDPSOCKET + } else { + while (!d->readNotificationActivated) { + if (!d->udpData->udpSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#endif // QT_NO_UDPSOCKET + } + + + bool ret = d->readNotificationActivated; + d->readNotificationActivated = false; + + QSOCKS5_DEBUG << "waitForRead returned" << ret; + return ret; +} + + +bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForWrite" << msecs; + + QElapsedTimer stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + + // flush any bytes we may still have buffered in the time that we have left + if (d->data->controlSocket->bytesToWrite()) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + while ((msecs == -1 || stopWatch.elapsed() < msecs) + && d->data->controlSocket->state() == QAbstractSocket::ConnectedState + && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize; +} + +bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + if (!checkWrite) { + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QSocks5SocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->readNotificationEnabled; +} + +void QSocks5SocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "setReadNotificationEnabled(" << enable << ')'; + + bool emitSignal = false; + if (!d->readNotificationEnabled + && enable) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + emitSignal = !d->connectData->readBuffer.isEmpty(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) + emitSignal = !d->udpData->pendingDatagrams.isEmpty(); +#endif + else if (d->mode == QSocks5SocketEnginePrivate::BindMode + && d->socketState == QAbstractSocket::ListeningState + && d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + emitSignal = true; + } + + d->readNotificationEnabled = enable; + + if (emitSignal) + d->emitReadNotification(); +} + +bool QSocks5SocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->writeNotificationEnabled; +} + +void QSocks5SocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->socketState == QAbstractSocket::ConnectedState) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode && d->data->controlSocket->bytesToWrite()) + return; // will be emitted as a result of bytes written + d->emitWriteNotification(); + d->writeNotificationActivated = false; + } +} + +bool QSocks5SocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->exceptNotificationEnabled; +} + +void QSocks5SocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->exceptNotificationEnabled = enable; +} + +QAbstractSocketEngine * +QSocks5SocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, QObject *parent) +{ + Q_UNUSED(socketType); + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::Socks5Proxy) { + QSOCKS5_DEBUG << "not proxying"; + return 0; + } + QScopedPointer<QSocks5SocketEngine> engine(new QSocks5SocketEngine(parent)); + engine->setProxy(proxy); + return engine.take(); +} + +QAbstractSocketEngine *QSocks5SocketEngineHandler::createSocketEngine(int socketDescriptor, QObject *parent) +{ + QSOCKS5_DEBUG << "createSocketEngine" << socketDescriptor; + if (socks5BindStore()->contains(socketDescriptor)) { + QSOCKS5_DEBUG << "bind store contains" << socketDescriptor; + return new QSocks5SocketEngine(parent); + } + return 0; +} + +#endif // QT_NO_SOCKS5 + +QT_END_NAMESPACE diff --git a/src/network/socket/qsocks5socketengine_p.h b/src/network/socket/qsocks5socketengine_p.h new file mode 100644 index 0000000000..9492d4532d --- /dev/null +++ b/src/network/socket/qsocks5socketengine_p.h @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** 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 QSOCKS5SOCKETENGINE_P_H +#define QSOCKS5SOCKETENGINE_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 "qabstractsocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SOCKS5 + +class QSocks5SocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QSocks5SocketEngine(QObject *parent = 0); + ~QSocks5SocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &interface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); +#endif // QT_NO_NETWORKINTERFACE + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + qint64 bytesToWrite() const; + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +private: + Q_DECLARE_PRIVATE(QSocks5SocketEngine) + Q_DISABLE_COPY(QSocks5SocketEngine) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketError(QAbstractSocket::SocketError)) +#ifndef QT_NO_UDPSOCKET + Q_PRIVATE_SLOT(d_func(), void _q_udpSocketReadNotification()) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketBytesWritten()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingWriteNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingConnectionNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketDisconnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketStateChanged(QAbstractSocket::SocketState)) + +}; + + +class QTcpSocket; + +class QSocks5Authenticator +{ +public: + QSocks5Authenticator(); + virtual ~QSocks5Authenticator(); + virtual char methodId(); + virtual bool beginAuthenticate(QTcpSocket *socket, bool *completed); + virtual bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + virtual bool seal(const QByteArray buf, QByteArray *sealedBuf); + virtual bool unSeal(const QByteArray sealedBuf, QByteArray *buf); + virtual bool unSeal(QTcpSocket *sealedSocket, QByteArray *buf); + + virtual QString errorString() { return QString(); } +}; + +class QSocks5PasswordAuthenticator : public QSocks5Authenticator +{ +public: + QSocks5PasswordAuthenticator(const QString &userName, const QString &password); + char methodId(); + bool beginAuthenticate(QTcpSocket *socket, bool *completed); + bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + QString errorString(); + +private: + QString userName; + QString password; +}; + +struct QSocks5Data; +struct QSocks5ConnectData; +struct QSocks5UdpAssociateData; +struct QSocks5BindData; + +class QSocks5SocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QSocks5SocketEngine) +public: + QSocks5SocketEnginePrivate(); + ~QSocks5SocketEnginePrivate(); + + enum Socks5State + { + Uninitialized = 0, + ConnectError, + AuthenticationMethodsSent, + Authenticating, + AuthenticatingError, + RequestMethodSent, + RequestError, + Connected, + UdpAssociateSuccess, + BindSuccess, + ControlSocketError, + SocksError, + HostNameLookupError + }; + Socks5State socks5State; + + enum Socks5Mode + { + NoMode, + ConnectMode, + BindMode, + UdpAssociateMode + }; + Socks5Mode mode; + + enum Socks5Error + { + SocksFailure = 0x01, + ConnectionNotAllowed = 0x02, + NetworkUnreachable = 0x03, + HostUnreachable = 0x04, + ConnectionRefused = 0x05, + TTLExpired = 0x06, + CommandNotSupported = 0x07, + AddressTypeNotSupported = 0x08, + LastKnownError = AddressTypeNotSupported, + UnknownError + }; + + void initialize(Socks5Mode socks5Mode); + + void setErrorState(Socks5State state, const QString &extraMessage = QString()); + void setErrorState(Socks5State state, Socks5Error socks5error); + + void reauthenticate(); + void parseAuthenticationMethodReply(); + void parseAuthenticatingReply(); + void sendRequestMethod(); + void parseRequestMethodReply(); + void parseNewConnection(); + + bool waitForConnected(int msecs, bool *timedOut); + + void _q_controlSocketConnected(); + void _q_controlSocketReadNotification(); + void _q_controlSocketError(QAbstractSocket::SocketError); +#ifndef QT_NO_UDPSOCKET + void checkForDatagrams() const; + void _q_udpSocketReadNotification(); +#endif + void _q_controlSocketBytesWritten(); + void _q_controlSocketDisconnected(); + void _q_controlSocketStateChanged(QAbstractSocket::SocketState); + + QNetworkProxy proxyInfo; + + bool readNotificationEnabled, writeNotificationEnabled, exceptNotificationEnabled; + + int socketDescriptor; + + QSocks5Data *data; + QSocks5ConnectData *connectData; +#ifndef QT_NO_UDPSOCKET + QSocks5UdpAssociateData *udpData; +#endif + QSocks5BindData *bindData; + QString peerName; + + mutable bool readNotificationActivated; + mutable bool writeNotificationActivated; + + bool readNotificationPending; + void _q_emitPendingReadNotification(); + void emitReadNotification(); + bool writeNotificationPending; + void _q_emitPendingWriteNotification(); + void emitWriteNotification(); + bool connectionNotificationPending; + void _q_emitPendingConnectionNotification(); + void emitConnectionNotification(); +}; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; + + +QT_END_NAMESPACE +#endif // QT_NO_SOCKS5 +#endif // QSOCKS5SOCKETENGINE_H diff --git a/src/network/socket/qsymbiansocketengine.cpp b/src/network/socket/qsymbiansocketengine.cpp new file mode 100644 index 0000000000..f1b2982626 --- /dev/null +++ b/src/network/socket/qsymbiansocketengine.cpp @@ -0,0 +1,1730 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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 QNATIVESOCKETENGINE_DEBUG +#include "qsymbiansocketengine_p.h" + +#include "qiodevice.h" +#include "qhostaddress.h" +#include "qelapsedtimer.h" +#include "qvarlengtharray.h" +#include "qnetworkinterface.h" +#include <private/qnetworksession_p.h> +#include <es_sock.h> +#include <in_sock.h> +#include <net/if.h> + +#include <private/qcore_symbian_p.h> + +#if !defined(QT_NO_NETWORKPROXY) +# include "qnetworkproxy.h" +# include "qabstractsocket.h" +# include "qtcpserver.h" +#endif + +#include <QCoreApplication> + +#include <qabstracteventdispatcher.h> +#include <private/qeventdispatcher_symbian_p.h> +#include <qsocketnotifier.h> +#include <qnetworkinterface.h> + +#include <private/qthread_p.h> +#include <private/qobject_p.h> +#include <private/qsystemerror_p.h> + +#if defined QNATIVESOCKETENGINE_DEBUG +#include <qstring.h> +#include <ctype.h> +#endif + +QT_BEGIN_NAMESPACE + +#define Q_VOID +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) + +#if defined QNATIVESOCKETENGINE_DEBUG + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +#endif + +void QSymbianSocketEnginePrivate::getPortAndAddress(const TInetAddr& a, quint16 *port, QHostAddress *addr) +{ + if (a.Family() == KAfInet6 && !a.IsV4Compat() && !a.IsV4Mapped()) { + Q_IPV6ADDR tmp; + memcpy(&tmp, a.Ip6Address().u.iAddr8, sizeof(tmp)); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(tmp); + *addr = tmpAddress; + TPckgBuf<TSoInetIfQuery> query; + query().iSrcAddr = a; + TInt err = nativeSocket.GetOpt(KSoInetIfQueryBySrcAddr, KSolInetIfQuery, query); + if (!err) + addr->setScopeId(qt_TDesC2QString(query().iName)); + else + addr->setScopeId(QString::number(a.Scope())); + } + if (port) + *port = a.Port(); + return; + } + if (port) + *port = a.Port(); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(a.Address()); + *addr = tmpAddress; + } +} +/*! \internal + + Creates and returns a new socket descriptor of type \a socketType + and \a socketProtocol. Returns -1 on failure. +*/ +bool QSymbianSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ + Q_Q(QSymbianSocketEngine); + TUint family = KAfInet; // KAfInet6 is only used as an address family, not as a protocol family + TUint type = (socketType == QAbstractSocket::UdpSocket) ? KSockDatagram : KSockStream; + TUint protocol = (socketType == QAbstractSocket::UdpSocket) ? KProtocolInetUdp : KProtocolInetTcp; + + //Check if there is a user specified session + QVariant v(q->property("_q_networksession")); + TInt err; + if (v.isValid()) { + QSharedPointer<QNetworkSession> s = qvariant_cast<QSharedPointer<QNetworkSession> >(v); + err = QNetworkSessionPrivate::nativeOpenSocket(*s, nativeSocket, family, type, protocol); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::createNewSocket - _q_networksession was set" << err; +#endif + } else + err = nativeSocket.Open(socketServer, family, type, protocol); //TODO: FIXME - deprecated API, make sure we always have a connection instead + + if (err != KErrNone) { + switch (err) { + case KErrNotSupported: + case KErrNotFound: + setError(QAbstractSocket::UnsupportedSocketOperationError, + ProtocolUnsupportedErrorString); + break; + default: + setError(err); + break; + } + + return false; + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::createNewSocket - created" << nativeSocket.SubSessionHandle(); +#endif + socketDescriptor = QSymbianSocketManager::instance().addSocket(nativeSocket); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << " - allocated socket descriptor" << socketDescriptor; +#endif + return true; +} + +void QSymbianSocketEnginePrivate::setPortAndAddress(TInetAddr& nativeAddr, quint16 port, const QHostAddress &addr) +{ + nativeAddr.SetPort(port); + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + TPckgBuf<TSoInetIfQuery> query; + query().iName = qt_QString2TPtrC(addr.scopeId()); + TInt err = nativeSocket.GetOpt(KSoInetIfQueryByName, KSolInetIfQuery, query); + if (!err) + nativeAddr.SetScope(query().iIndex); + else + nativeAddr.SetScope(0); + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + TIp6Addr v6addr; + memcpy(v6addr.u.iAddr8, ip6.c, 16); + nativeAddr.SetAddress(v6addr); + } else if (addr.protocol() == QAbstractSocket::IPv4Protocol) { + nativeAddr.SetAddress(addr.toIPv4Address()); + } else { + qWarning("unsupported network protocol (%d)", addr.protocol()); + } +} + +QSymbianSocketEnginePrivate::QSymbianSocketEnginePrivate() : + socketDescriptor(-1), + socketServer(QSymbianSocketManager::instance().getSocketServer()), + readNotificationsEnabled(false), + writeNotificationsEnabled(false), + exceptNotificationsEnabled(false), + asyncSelect(0) +{ +} + +QSymbianSocketEnginePrivate::~QSymbianSocketEnginePrivate() +{ +} + + +QSymbianSocketEngine::QSymbianSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QSymbianSocketEnginePrivate(), parent) +{ +} + + +QSymbianSocketEngine::~QSymbianSocketEngine() +{ + close(); +} + +/*! + Initializes a QSymbianSocketEngine by creating a new socket of type \a + socketType and network layer protocol \a protocol. Returns true on + success; otherwise returns false. + + If the socket was already initialized, this function closes the + socket before reeinitializing it. + + The new socket is non-blocking, and for UDP sockets it's also + broadcast enabled. +*/ +bool QSymbianSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QSymbianSocketEngine); + if (isValid()) + close(); + + // Create the socket + if (!d->createNewSocket(socketType, protocol)) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString typeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + QString protocolStr = QLatin1String("UnknownProtocol"); + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + qDebug("QSymbianSocketEngine::initialize(type == %s, protocol == %s) failed: %s", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->BroadcastingInitFailedErrorString); + close(); + return false; + } + + + // Make sure we receive out-of-band data + if (socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QSymbianSocketEngine::initialize unable to inline out-of-band data"); + } + + + d->socketType = socketType; + d->socketProtocol = protocol; + return true; +} + +/*! \overload + + Initializes the socket using \a socketDescriptor instead of + creating a new one. The socket type and network layer protocol are + determined automatically. The socket's state is set to \a + socketState. + + If the socket type is either TCP or UDP, it is made non-blocking. + UDP sockets are also broadcast enabled. + */ +bool QSymbianSocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QSymbianSocketEngine); + + if (isValid()) + close(); + + if (!QSymbianSocketManager::instance().lookupSocket(socketDescriptor, d->nativeSocket)) { + qWarning("QSymbianSocketEngine::initialize - socket descriptor not found"); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSymbianSocketEnginePrivate::InvalidSocketErrorString); + return false; + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::initialize - attached to" << d->nativeSocket.SubSessionHandle() << socketDescriptor; +#endif + Q_ASSERT(d->socketDescriptor == socketDescriptor || d->socketDescriptor == -1); + d->socketDescriptor = socketDescriptor; + + // determine socket type and protocol + if (!d->fetchConnectionParameters()) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::initialize(socketDescriptor == %i) failed: %s", + socketDescriptor, d->socketErrorString.toLatin1().constData()); +#endif + d->socketDescriptor = -1; + return false; + } + + if (d->socketType != QAbstractSocket::UnknownSocketType) { + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (d->socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + d->BroadcastingInitFailedErrorString); + close(); + return false; + } + + // Make sure we receive out-of-band data + if (d->socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QSymbianSocketEngine::initialize unable to inline out-of-band data"); + } + } + + d->socketState = socketState; + return true; +} + +/*! + Returns true if the socket is valid; otherwise returns false. A + socket is valid if it has not been successfully initialized, or if + it has been closed. +*/ +bool QSymbianSocketEngine::isValid() const +{ + Q_D(const QSymbianSocketEngine); + return d->socketDescriptor != -1; +} + + +/*! + Returns the native socket descriptor. Any use of this descriptor + stands the risk of being non-portable. +*/ +int QSymbianSocketEngine::socketDescriptor() const +{ + Q_D(const QSymbianSocketEngine); + return d->socketDescriptor; +} + +/* + Sets the socket option \a opt to \a v. +*/ +bool QSymbianSocketEngine::setOption(QAbstractSocketEngine::SocketOption opt, int v) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setOption(), false); + + TUint n = 0; + TUint level = KSOLSocket; // default + + if (!QSymbianSocketEnginePrivate::translateSocketOption(opt, n, level)) + return false; + + if (!level && !n) + return true; + + return (KErrNone == d->nativeSocket.SetOpt(n, level, v)); +} + +/* + Returns the value of the socket option \a opt. +*/ +int QSymbianSocketEngine::option(QAbstractSocketEngine::SocketOption opt) const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::option(), -1); + + TUint n; + TUint level = KSOLSocket; // default + + if (!QSymbianSocketEnginePrivate::translateSocketOption(opt, n, level)) + return false; + + if (!level && !n) + return 1; + + int v = -1; + //GetOpt() is non const + TInt err = d->nativeSocket.GetOpt(n, level, v); + if (!err) + return v; + + return -1; +} + +bool QSymbianSocketEnginePrivate::translateSocketOption(QAbstractSocketEngine::SocketOption opt, TUint &n, TUint &level) +{ + + switch (opt) { + case QAbstractSocketEngine::ReceiveBufferSocketOption: + n = KSORecvBuf; + break; + case QAbstractSocketEngine::SendBufferSocketOption: + n = KSOSendBuf; + break; + case QAbstractSocketEngine::NonBlockingSocketOption: + n = KSONonBlockingIO; + break; + case QAbstractSocketEngine::AddressReusable: + level = KSolInetIp; + n = KSoReuseAddr; + break; + case QAbstractSocketEngine::BroadcastSocketOption: + case QAbstractSocketEngine::BindExclusively: + level = 0; + n = 0; + return true; + case QAbstractSocketEngine::ReceiveOutOfBandData: + level = KSolInetTcp; + n = KSoTcpOobInline; + break; + case QAbstractSocketEngine::LowDelayOption: + level = KSolInetTcp; + n = KSoTcpNoDelay; + break; + case QAbstractSocketEngine::KeepAliveOption: + level = KSolInetTcp; + n = KSoTcpKeepAlive; + break; + case QAbstractSocketEngine::MulticastLoopbackOption: + level = KSolInetIp; + n = KSoIp6MulticastLoop; + break; + case QAbstractSocketEngine::MulticastTtlOption: + level = KSolInetIp; + n = KSoIp6MulticastHops; + break; + default: + return false; + } + return true; +} + +qint64 QSymbianSocketEngine::receiveBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::receiveBufferSize(), -1); + return option(ReceiveBufferSocketOption); +} + +void QSymbianSocketEngine::setReceiveBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setReceiveBufferSize(), Q_VOID); + setOption(ReceiveBufferSocketOption, size); +} + +qint64 QSymbianSocketEngine::sendBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setSendBufferSize(), -1); + return option(SendBufferSocketOption); +} + +void QSymbianSocketEngine::setSendBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setSendBufferSize(), Q_VOID); + setOption(SendBufferSocketOption, size); +} + +/*! + Connects to the remote host name given by \a name on port \a + port. When this function is called, the upper-level will not + perform a hostname lookup. + + The native socket engine does not support this operation, + but some other socket engines (notably proxy-based ones) do. +*/ +bool QSymbianSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_UNUSED(name); + Q_UNUSED(port); + Q_D(QSymbianSocketEngine); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSymbianSocketEnginePrivate::OperationUnsupportedErrorString); + return false; +} + +/*! + If there's a connection activity on the socket, process it. Then + notify our parent if there really was activity. +*/ +void QSymbianSocketEngine::connectionNotification() +{ + // FIXME check if we really need to do it like that in Symbian + Q_D(QSymbianSocketEngine); + Q_ASSERT(state() == QAbstractSocket::ConnectingState); + + connectToHost(d->peerAddress, d->peerPort); + if (state() != QAbstractSocket::ConnectingState) { + // we changed states + QAbstractSocketEngine::connectionNotification(); + } +} + + +bool QSymbianSocketEngine::connectToHost(const QHostAddress &addr, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::connectToHost(), false); + +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug("QSymbianSocketEngine::connectToHost() : %d ", d->socketDescriptor); +#endif + + if (!d->checkProxy(addr)) + return false; + + d->peerAddress = addr; + d->peerPort = port; + + TInetAddr nativeAddr; + d->setPortAndAddress(nativeAddr, port, addr); + TRequestStatus status; + d->nativeSocket.Connect(nativeAddr, status); + User::WaitForRequest(status); + TInt err = status.Int(); + //For non blocking connect, KErrAlreadyExists is returned from the second Connect() to indicate + //the connection is up. So treat this the same as KErrNone which would be returned from the first + //call if it wouldn't block. (e.g. winsock wrapper in the emulator ignores the nonblocking flag) + if (err && err != KErrAlreadyExists) { + switch (err) { + case KErrWouldBlock: + d->socketState = QAbstractSocket::ConnectingState; + break; + default: + d->setError(err); + d->socketState = QAbstractSocket::UnconnectedState; + break; + } + + if (d->socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::connectToHost(%s, %i) == false (%s)", + addr.toString().toLatin1().constData(), port, + d->socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::Connect(%s, %i) == true", + addr.toString().toLatin1().constData(), port); +#endif + + d->socketState = QAbstractSocket::ConnectedState; + d->fetchConnectionParameters(); + return true; +} + +bool QSymbianSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::bind(), false); + + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATE(QSymbianSocketEngine::bind(), QAbstractSocket::UnconnectedState, false); + + TInetAddr nativeAddr; + if (address == QHostAddress::Any || address == QHostAddress::AnyIPv6) { + //Should allow both IPv4 and IPv6 + //Listening on "0.0.0.0" accepts ONLY ipv4 connections + //Listening on "::" accepts ONLY ipv6 connections + nativeAddr.SetFamily(KAFUnspec); + nativeAddr.SetPort(port); + } else { + d->setPortAndAddress(nativeAddr, port, address); + } + + TInt err = d->nativeSocket.Bind(nativeAddr); +#ifdef __WINS__ + if (err == KErrArgument) // winsock prt returns wrong error code + err = KErrInUse; +#endif + + if (err) { + switch (err) { + case KErrNotFound: + // the specified interface was not found - use the error code expected + d->setError(QAbstractSocket::SocketAddressNotAvailableError, QSymbianSocketEnginePrivate::AddressNotAvailableErrorString); + break; + default: + d->setError(err); + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, d->socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + d->socketState = QAbstractSocket::BoundState; + + d->fetchConnectionParameters(); + + // When we bind to unspecified address (to get a dual mode socket), report back the + // same type of address that was requested. This is required for SOCKS proxy to work. + if (nativeAddr.Family() == KAFUnspec) + d->localAddress = address; + return true; +} + +bool QSymbianSocketEngine::listen() +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::listen(), false); + Q_CHECK_STATE(QSymbianSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + TInt err = d->nativeSocket.Listen(50); + if (err) { + d->setError(err); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::listen() == false (%s)", + d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::listen() == true"); +#endif + + d->socketState = QAbstractSocket::ListeningState; + return true; +} + +int QSymbianSocketEngine::accept() +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::accept(), -1); + Q_CHECK_STATE(QSymbianSocketEngine::accept(), QAbstractSocket::ListeningState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::accept(), QAbstractSocket::TcpSocket, false); + RSocket blankSocket; + blankSocket.Open(d->socketServer); + TRequestStatus status; + d->nativeSocket.Accept(blankSocket, status); + User::WaitForRequest(status); + if (status.Int()) { + blankSocket.Close(); + if (status != KErrWouldBlock) + qWarning("QSymbianSocketEngine::accept() - error %d", status.Int()); + return -1; + } + +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::accept - created" << blankSocket.SubSessionHandle(); +#endif + int fd = QSymbianSocketManager::instance().addSocket(blankSocket); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << " - allocated socket descriptor" << fd; +#endif + return fd; +} + +qint64 QSymbianSocketEngine::bytesAvailable() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::bytesAvailable(), -1); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, false); + int nbytes = 0; + qint64 available = 0; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending, KSOLSocket, nbytes); + if (err) + return 0; + available = (qint64) nbytes; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::bytesAvailable() == %lli", available); +#endif + return available; +} + +bool QSymbianSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::hasPendingDatagrams(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UnconnectedState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + int nbytes; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending,KSOLSocket, nbytes); + return err == KErrNone && nbytes > 0; +} + +qint64 QSymbianSocketEngine::pendingDatagramSize() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::pendingDatagramSize(), false); + Q_CHECK_TYPE(QSymbianSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + int nbytes; + TInt err = d->nativeSocket.GetOpt(KSOReadBytesPending,KSOLSocket, nbytes); + if (nbytes > 0) { + //nbytes includes IP header, which is of variable length (IPv4 with or without options, IPv6...) + QByteArray next(nbytes,0); + TPtr8 buffer((TUint8*)next.data(), next.size()); + TInetAddr addr; + TRequestStatus status; + //TODO: rather than peek, should we save this for next call to readDatagram? + //what if calls don't match though? + d->nativeSocket.RecvFrom(buffer, addr, KSockReadPeek, status); + User::WaitForRequest(status); + if (status.Int()) + return 0; + return buffer.Length(); + } + return qint64(nbytes); +} + + +qint64 QSymbianSocketEngine::readDatagram(char *data, qint64 maxSize, + QHostAddress *address, quint16 *port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::readDatagram(), -1); + Q_CHECK_TYPE(QSymbianSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, false); + TPtr8 buffer((TUint8*)data, (int)maxSize); + TInetAddr addr; + TRequestStatus status; + d->nativeSocket.RecvFrom(buffer, addr, 0, status); + User::WaitForRequest(status); //Non blocking receive + + if (status.Int()) { + d->setError(QAbstractSocket::NetworkError, d->ReceiveDatagramErrorString); + } else if (port || address) { + d->getPortAndAddress(addr, port, address); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + int len = buffer.Length(); + qDebug("QSymbianSocketEngine::receiveDatagram(%p \"%s\", %lli, %s, %i) == %lli", + data, qt_prettyDebug(data, qMin(len, ssize_t(16)), len).data(), maxSize, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, (qint64) len); +#endif + + if (status.Int()) + return -1; + return qint64(buffer.Length()); +} + + +qint64 QSymbianSocketEngine::writeDatagram(const char *data, qint64 len, + const QHostAddress &host, quint16 port) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::writeDatagram(), -1); + Q_CHECK_TYPE(QSymbianSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + TPtrC8 buffer((TUint8*)data, (int)len); + TInetAddr addr; + d->setPortAndAddress(addr, port, host); + TSockXfrLength sentBytes; + TRequestStatus status; + d->nativeSocket.SendTo(buffer, addr, 0, status, sentBytes); + User::WaitForRequest(status); //Non blocking send + TInt err = status.Int(); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::writeDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli (err=%d)", data, + qt_prettyDebug(data, qMin<int>(len, 16), len).data(), len, host.toString().toLatin1().constData(), + port, (qint64) sentBytes(), err); +#endif + + if (err) { + switch (err) { + case KErrWouldBlock: + // do not error the socket. (otherwise socket layer is reset) + // On symbian^1 and earlier, KErrWouldBlock is returned when interface is not up yet + // On symbian^3, KErrNone is returned but sentBytes = 0 + return 0; + case KErrTooBig: + d->setError(QAbstractSocket::DatagramTooLargeError, d->DatagramTooLargeErrorString); + break; + default: + d->setError(QAbstractSocket::NetworkError, d->SendDatagramErrorString); + } + return -1; + } + + if (QSysInfo::s60Version() <= QSysInfo::SV_S60_5_0) { + // This is evil hack, but for some reason native RSocket::SendTo returns 0, + // for large datagrams (such as 600 bytes). Based on comments from Open C team + // this should happen only in platforms <= S60 5.0. + return len; + } + return sentBytes(); +} + +// FIXME check where the native socket engine called that.. +bool QSymbianSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + + if (!nativeSocket.SubSessionHandle()) { + if (!QSymbianSocketManager::instance().lookupSocket(socketDescriptor, nativeSocket)) { + setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString); + return false; + } + } + + // Determine local address + TSockAddr addr; + nativeSocket.LocalName(addr); + getPortAndAddress(addr, &localPort, &localAddress); + + // Determine protocol family + socketProtocol = localAddress.protocol(); + + // Determine the remote address + nativeSocket.RemoteName(addr); + getPortAndAddress(addr, &peerPort, &peerAddress); + + // Determine the socket type (UDP/TCP) + TProtocolDesc protocol; + TInt err = nativeSocket.Info(protocol); + if (err) { + setError(err); + return false; + } else { + switch (protocol.iProtocol) { + case KProtocolInetTcp: + socketType = QAbstractSocket::TcpSocket; + break; + case KProtocolInetUdp: + socketType = QAbstractSocket::UdpSocket; + break; + default: + socketType = QAbstractSocket::UnknownSocketType; + break; + } + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = QLatin1String("UnknownProtocol"); + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = QLatin1String("IPv4Protocol"); + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = QLatin1String("IPv6Protocol"); + + QString socketTypeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = QLatin1String("UdpSocket"); + + qDebug("QSymbianSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," + " peer == %s:%i, socket == %s - %s", + localAddress.toString().toLatin1().constData(), localPort, + peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(), + socketProtocolStr.toLatin1().constData()); +#endif + return true; +} + +void QSymbianSocketEngine::close() +{ + if (!isValid()) + return; + Q_D(QSymbianSocketEngine); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::close()"); +#endif + + d->readNotificationsEnabled = false; + d->writeNotificationsEnabled = false; + d->exceptNotificationsEnabled = false; + if (d->asyncSelect) { + d->asyncSelect->deleteLater(); + d->asyncSelect = 0; + } + + //TODO: call nativeSocket.Shutdown(EImmediate) in some cases? + if (d->socketType == QAbstractSocket::UdpSocket) { + //TODO: Close hangs without this, but only for UDP - why? + TRequestStatus stat; + d->nativeSocket.Shutdown(RSocket::EImmediate, stat); + User::WaitForRequest(stat); + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::close - closing socket" << d->nativeSocket.SubSessionHandle() << d->socketDescriptor; +#endif + //remove must come before close to avoid a race where another thread gets the old subsession handle + //reused & asserts when calling QSymbianSocketManager::instance->addSocket + QSymbianSocketManager::instance().removeSocket(d->nativeSocket); + d->nativeSocket.Close(); + d->socketDescriptor = -1; + + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); +} + +qint64 QSymbianSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::write(), -1); + Q_CHECK_STATE(QSymbianSocketEngine::write(), QAbstractSocket::ConnectedState, -1); + TPtrC8 buffer((TUint8*)data, (int)len); + TSockXfrLength sentBytes = 0; + TRequestStatus status; + d->nativeSocket.Send(buffer, 0, status, sentBytes); + User::WaitForRequest(status); //TODO: on emulator this blocks for write >16kB (non blocking IO not implemented properly?) + TInt err = status.Int(); + + if (err) { + switch (err) { + case KErrDisconnected: + case KErrEof: + sentBytes = -1; + d->setError(QAbstractSocket::RemoteHostClosedError, d->RemoteHostClosedErrorString); + close(); + break; + case KErrTooBig: + d->setError(QAbstractSocket::DatagramTooLargeError, d->DatagramTooLargeErrorString); + break; + case KErrWouldBlock: + break; + default: + sentBytes = -1; + d->setError(err); + close(); + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::write(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin((int) len, 16), + (int) len).data(), len, (int) sentBytes()); +#endif + + return qint64(sentBytes()); +} +/* +*/ +qint64 QSymbianSocketEngine::read(char *data, qint64 maxSize) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::read(), -1); + Q_CHECK_STATES(QSymbianSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); + + TPtr8 buffer((TUint8*)data, (int)maxSize); + TSockXfrLength received = 0; + TRequestStatus status; + TSockAddr dummy; + if (d->socketType == QAbstractSocket::UdpSocket) { + //RecvOneOrMore() can only be used with stream-interfaced connected sockets; datagram interface sockets will return KErrNotSupported. + d->nativeSocket.RecvFrom(buffer, dummy, 0, status); + } else { + d->nativeSocket.RecvOneOrMore(buffer, 0, status, received); + } + User::WaitForRequest(status); //Non blocking receive + TInt err = status.Int(); + int r = buffer.Length(); + + if (err == KErrWouldBlock) { + // No data was available for reading + r = -2; + } else if (err != KErrNone) { + d->setError(err); + close(); + r = -1; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QSymbianSocketEngine::read(%p \"%s\", %llu) == %i (err = %d)", + data, qt_prettyDebug(data, qMin(r, ssize_t(16)), r).data(), + maxSize, r, err); +#endif + + return qint64(r); +} + +int QSymbianSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + bool readyRead = false; + bool readyWrite = false; + if (selectForRead) + return nativeSelect(timeout, true, false, &readyRead, &readyWrite); + else + return nativeSelect(timeout, false, true, &readyRead, &readyWrite); +} + +/*! + \internal + \param timeout timeout in milliseconds + \param checkRead caller is interested if the socket is ready to read + \param checkWrite caller is interested if the socket is ready for write + \param selectForRead (out) should set to true if ready to read + \param selectForWrite (out) should set to true if ready to write + \return 0 on timeout, >0 on success, <0 on error + */ +int QSymbianSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + //cancel asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->Cancel(); + + TPckgBuf<TUint> selectFlags; + selectFlags() = KSockSelectExcept; + if (checkRead) + selectFlags() |= KSockSelectRead; + if (checkWrite) + selectFlags() |= KSockSelectWrite; + TInt err; + if (timeout == 0) { + //if timeout is zero, poll + err = nativeSocket.GetOpt(KSOSelectPoll, KSOLSocket, selectFlags); + } else { + TRequestStatus selectStat; + nativeSocket.Ioctl(KIOctlSelect, selectStat, &selectFlags, KSOLSocket); + + if (timeout < 0) + User::WaitForRequest(selectStat); //negative means no timeout + else { + if (!selectTimer.Handle()) + qt_symbian_throwIfError(selectTimer.CreateLocal()); + TRequestStatus timerStat; + selectTimer.HighRes(timerStat, timeout * 1000); + User::WaitForRequest(timerStat, selectStat); + if (selectStat == KRequestPending) { + nativeSocket.CancelIoctl(); + //CancelIoctl completes the request (most likely with KErrCancel) + //We need to wait for this to keep the thread semaphore balanced (or active scheduler will panic) + User::WaitForRequest(selectStat); + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); + #ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select timeout"; + #endif + return 0; //timeout + } else { + selectTimer.Cancel(); + User::WaitForRequest(timerStat); + } + } + + #ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select status" << selectStat.Int() << (int)selectFlags(); + #endif + err = selectStat.Int(); + } + + if (!err && (selectFlags() & KSockSelectExcept)) { + nativeSocket.GetOpt(KSOSelectLastError, KSOLSocket, err); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEnginePrivate::nativeSelect: select last error" << err; +#endif + } + if (err) { + //TODO: avoidable cast? + //set the error here, because read won't always return the same error again as select. + const_cast<QSymbianSocketEnginePrivate*>(this)->setError(err); + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); //TODO: in error case should we restart or not? + return err; + } + if (checkRead && (selectFlags() & KSockSelectRead)) { + Q_ASSERT(selectForRead); + *selectForRead = true; + } + if (checkWrite && (selectFlags() & KSockSelectWrite)) { + Q_ASSERT(selectForWrite); + *selectForWrite = true; + } + //restart asynchronous notifier (only one IOCTL allowed at a time) + if (asyncSelect) + asyncSelect->IssueRequest(); + return 1; +} + +bool QSymbianSocketEngine::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::joinMulticastGroup(), false); + Q_CHECK_STATE(QSymbianSocketEngine::joinMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::joinMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->multicastGroupMembershipHelper(groupAddress, iface, KSoIp6JoinGroup); +} + +bool QSymbianSocketEngine::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::leaveMulticastGroup(), false); + Q_CHECK_STATE(QSymbianSocketEngine::leaveMulticastGroup(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QSymbianSocketEngine::leaveMulticastGroup(), QAbstractSocket::UdpSocket, false); + return d->multicastGroupMembershipHelper(groupAddress, iface, KSoIp6LeaveGroup); +} + +bool QSymbianSocketEnginePrivate::multicastGroupMembershipHelper(const QHostAddress &groupAddress, + const QNetworkInterface &iface, + TUint operation) +{ +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug() << "QSymbianSocketEnginePrivate::multicastGroupMembershipHelper" << groupAddress << iface << operation; +#endif + //translate address + TPckgBuf<TIp6Mreq> option; + if (groupAddress.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR ip6 = groupAddress.toIPv6Address(); + memcpy(option().iAddr.u.iAddr8, ip6.c, 16); + } else { + TInetAddr wrapped; + wrapped.SetAddress(groupAddress.toIPv4Address()); + wrapped.ConvertToV4Mapped(); + option().iAddr = wrapped.Ip6Address(); + } + option().iInterface = iface.index(); + //join or leave group + TInt err = nativeSocket.SetOpt(operation, KSolInetIp, option); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug() << "address" << qt_prettyDebug((const char *)(option().iAddr.u.iAddr8), 16, 16); + qDebug() << "interface" << option().iInterface; + qDebug() << "error" << err; +#endif + if (err) { + setError(err); + } + return (KErrNone == err); +} + +QNetworkInterface QSymbianSocketEngine::multicastInterface() const +{ + //TODO + const Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::multicastInterface(), QNetworkInterface()); + Q_CHECK_TYPE(QSymbianSocketEngine::multicastInterface(), QAbstractSocket::UdpSocket, QNetworkInterface()); + return QNetworkInterface(); +} + +bool QSymbianSocketEngine::setMulticastInterface(const QNetworkInterface &iface) +{ + //TODO - this is possibly a unix'ism as the RConnection on which the socket was created is probably controlling this + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setMulticastInterface(), false); + Q_CHECK_TYPE(QSymbianSocketEngine::setMulticastInterface(), QAbstractSocket::UdpSocket, false); + return false; +} + +bool QSymbianSocketEnginePrivate::checkProxy(const QHostAddress &address) +{ + if (address == QHostAddress::LocalHost || address == QHostAddress::LocalHostIPv6) + return true; + +#if !defined(QT_NO_NETWORKPROXY) + QObject *parent = q_func()->parent(); + QNetworkProxy proxy; + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(parent)) { + proxy = socket->proxy(); + } else if (QTcpServer *server = qobject_cast<QTcpServer *>(parent)) { + proxy = server->proxy(); + } else { + // no parent -> no proxy + return true; + } + + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() != QNetworkProxy::DefaultProxy && + proxy.type() != QNetworkProxy::NoProxy) { + // QSymbianSocketEngine doesn't do proxies + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidProxyTypeString); + return false; + } +#endif + + return true; +} + +// FIXME this is also in QNativeSocketEngine, unify it +/*! \internal + + Sets the error and error string if not set already. The only + interesting error is the first one that occurred, and not the last + one. +*/ +void QSymbianSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + // Another exception is the way the waitFor*() functions set + // an error when a timeout occurs. After the call to setError() + // they reset the hasSetSocketError to false + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to initialize non-blocking socket"); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to initialize broadcast socket"); + break; + case NoIpV6ErrorString: + socketErrorString = QSymbianSocketEngine::tr("Attempt to use IPv6 socket on a platform with no IPv6 support"); + break; + case RemoteHostClosedErrorString: + socketErrorString = QSymbianSocketEngine::tr("The remote host closed the connection"); + break; + case TimeOutErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network operation timed out"); + break; + case ResourceErrorString: + socketErrorString = QSymbianSocketEngine::tr("Out of resources"); + break; + case OperationUnsupportedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unsupported socket operation"); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Protocol type not supported"); + break; + case InvalidSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Invalid socket descriptor"); + break; + case HostUnreachableErrorString: + socketErrorString = QSymbianSocketEngine::tr("Host unreachable"); + break; + case NetworkUnreachableErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network unreachable"); + break; + case AccessErrorString: + socketErrorString = QSymbianSocketEngine::tr("Permission denied"); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QSymbianSocketEngine::tr("Connection timed out"); + break; + case ConnectionRefusedErrorString: + socketErrorString = QSymbianSocketEngine::tr("Connection refused"); + break; + case AddressInuseErrorString: + socketErrorString = QSymbianSocketEngine::tr("The bound address is already in use"); + break; + case AddressNotAvailableErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is not available"); + break; + case AddressProtectedErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is protected"); + break; + case DatagramTooLargeErrorString: + socketErrorString = QSymbianSocketEngine::tr("Datagram was too large to send"); + break; + case SendDatagramErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to send a message"); + break; + case ReceiveDatagramErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to receive a message"); + break; + case WriteErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unable to write"); + break; + case ReadErrorString: + socketErrorString = QSymbianSocketEngine::tr("Network error"); + break; + case PortInuseErrorString: + socketErrorString = QSymbianSocketEngine::tr("Another socket is already listening on the same port"); + break; + case NotSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Operation on non-socket"); + break; + case InvalidProxyTypeString: + socketErrorString = QSymbianSocketEngine::tr("The proxy type is invalid for this operation"); + break; + case InvalidAddressErrorString: + socketErrorString = QSymbianSocketEngine::tr("The address is invalid for this operation"); + break; + case SessionNotOpenErrorString: + socketErrorString = QSymbianSocketEngine::tr("The specified network session is not opened"); + break; + case UnknownSocketErrorString: + socketErrorString = QSymbianSocketEngine::tr("Unknown error"); + break; + } +} + +void QSymbianSocketEnginePrivate::setError(TInt symbianError) +{ + switch (symbianError) { + case KErrDisconnected: + case KErrEof: + case KErrConnectionTerminated: //interface stopped externally - RConnection::Stop(EStopAuthoritative) + setError(QAbstractSocket::RemoteHostClosedError, + QSymbianSocketEnginePrivate::RemoteHostClosedErrorString); + break; + case KErrNetUnreach: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::NetworkUnreachableErrorString); + break; + case KErrHostUnreach: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::HostUnreachableErrorString); + break; + case KErrNoProtocolOpt: + setError(QAbstractSocket::NetworkError, + QSymbianSocketEnginePrivate::ProtocolUnsupportedErrorString); + break; + case KErrInUse: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case KErrPermissionDenied: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + break; + case KErrNotSupported: + setError(QAbstractSocket::UnsupportedSocketOperationError, OperationUnsupportedErrorString); + break; + case KErrNoMemory: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case KErrCouldNotConnect: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + break; + case KErrTimedOut: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case KErrBadName: + setError(QAbstractSocket::NetworkError, InvalidAddressErrorString); + break; + default: + socketError = QAbstractSocket::NetworkError; + socketErrorString = QSystemError(symbianError, QSystemError::NativeError).toString(); + break; + } + hasSetSocketError = true; +} + +void QSymbianSocketEngine::startNotifications() +{ + Q_D(QSymbianSocketEngine); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::startNotifications" << d->readNotificationsEnabled << d->writeNotificationsEnabled << d->exceptNotificationsEnabled; +#endif + if (!d->asyncSelect && (d->readNotificationsEnabled || d->writeNotificationsEnabled || d->exceptNotificationsEnabled)) { + if (d->threadData->eventDispatcher) { + d->asyncSelect = q_check_ptr(new QAsyncSelect( + static_cast<QEventDispatcherSymbian*> (d->threadData->eventDispatcher), d->nativeSocket, + this)); + } else { + // call again when event dispatcher has been created + QMetaObject::invokeMethod(this, "startNotifications", Qt::QueuedConnection); + } + } + if (d->asyncSelect) + d->asyncSelect->IssueRequest(); +} + +bool QSymbianSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isReadNotificationEnabled(), false); + return d->readNotificationsEnabled; +} + +void QSymbianSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setReadNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setReadNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->readNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isWriteNotificationEnabled(), false); + return d->writeNotificationsEnabled; +} + +void QSymbianSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setWriteNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setWriteNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->writeNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::isExceptionNotificationEnabled(), false); + return d->exceptNotificationsEnabled; + return false; +} + +void QSymbianSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::setExceptionNotificationEnabled(), Q_VOID); +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::setExceptionNotificationEnabled" << enable << "socket" << d->socketDescriptor; +#endif + d->exceptNotificationsEnabled = enable; + startNotifications(); +} + +bool QSymbianSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, true); + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QSymbianSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForWrite(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, false); + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QSymbianSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_D(QSymbianSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QSymbianSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QSymbianSocketEngine::waitForReadOrWrite(), + QAbstractSocket::UnconnectedState, false); + + int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite); + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + d->TimeOutErrorString); + d->hasSetSocketError = false; // A timeout error is temporary in waitFor functions + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +qint64 QSymbianSocketEngine::bytesToWrite() const +{ + // This is what the QNativeSocketEngine does + return 0; +} + +bool QSymbianSocketEngine::event(QEvent* ev) +{ + Q_D(QSymbianSocketEngine); + if (ev->type() == QEvent::ThreadChange) { +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QSymbianSocketEngine::event - ThreadChange" << d->readNotificationsEnabled << d->writeNotificationsEnabled << d->exceptNotificationsEnabled; +#endif + if (d->asyncSelect) { + delete d->asyncSelect; + d->asyncSelect = 0; + // recreate select in new thread (because it is queued, the method is called in the new thread context) + QMetaObject::invokeMethod(this, "startNotifications", Qt::QueuedConnection); + } + d->selectTimer.Close(); + return true; + } + return QAbstractSocketEngine::event(ev); +} + +QAsyncSelect::QAsyncSelect(QEventDispatcherSymbian *dispatcher, RSocket& sock, QSymbianSocketEngine *parent) + : QActiveObject(CActive::EPriorityStandard, dispatcher), + m_inSocketEvent(false), + m_deleteLater(false), + m_socket(sock), + m_selectFlags(0), + engine(parent) +{ + CActiveScheduler::Add(this); +} + +QAsyncSelect::~QAsyncSelect() +{ + Cancel(); +} + +void QAsyncSelect::DoCancel() +{ + m_socket.CancelIoctl(); +} + +void QAsyncSelect::RunL() +{ + QT_TRYCATCH_LEAVING(run()); +} + +//RunError is called by the active scheduler if RunL leaves. +//Typically this will happen if a std::bad_alloc propagates down from the application +TInt QAsyncSelect::RunError(TInt aError) +{ + if (engine) { + QT_TRY { + engine->d_func()->setError(aError); + if (engine->isExceptionNotificationEnabled()) + engine->exceptionNotification(); + if (engine->isReadNotificationEnabled()) + engine->readNotification(); + } + QT_CATCH(...) {} + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::RunError" << aError; +#endif + return KErrNone; +} + +void QAsyncSelect::run() +{ + //when event loop disabled socket events, defer until later + if (maybeDeferSocketEvent()) + return; + m_inSocketEvent = true; + m_selectBuf() &= m_selectFlags; //the select ioctl reports everything, so mask to only what we requested + //KSockSelectReadContinuation is for reading datagrams in a mode that doesn't discard when the + //datagram is larger than the read buffer - Qt doesn't need to use this. + if (engine && engine->isReadNotificationEnabled() + && ((m_selectBuf() & KSockSelectRead) || iStatus != KErrNone)) { + engine->readNotification(); + } + if (engine && engine->isWriteNotificationEnabled() + && ((m_selectBuf() & KSockSelectWrite) || iStatus != KErrNone)) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->writeNotification(); + } + if (engine && engine->isExceptionNotificationEnabled() + && ((m_selectBuf() & KSockSelectExcept) || iStatus != KErrNone)) { + engine->exceptionNotification(); + } + m_inSocketEvent = false; + if (m_deleteLater) { + delete this; + return; + } + // select again (unless disabled by one of the callbacks) + IssueRequest(); +} + +void QAsyncSelect::deleteLater() +{ + if (m_inSocketEvent) { + engine = 0; + m_deleteLater = true; + } else { + delete this; + } +} + +void QAsyncSelect::IssueRequest() +{ + if (m_inSocketEvent) + return; //prevent thrashing during a callback - socket engine enables/disables multiple notifiers + TUint selectFlags = 0; + if (engine->isReadNotificationEnabled()) + selectFlags |= KSockSelectRead; + if (engine->isWriteNotificationEnabled()) + selectFlags |= KSockSelectWrite; + if (engine->isExceptionNotificationEnabled()) + selectFlags |= KSockSelectExcept; + if (selectFlags != m_selectFlags) { +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::IssueRequest() - select flags" << m_selectFlags << "->" << selectFlags; +#endif + Cancel(); + m_selectFlags = selectFlags; + } + if (m_selectFlags && !IsActive()) { + //always request errors (write notification does not complete on connect errors) + m_selectBuf() = m_selectFlags | KSockSelectExcept; + m_socket.Ioctl(KIOctlSelect, iStatus, &m_selectBuf, KSOLSocket); + SetActive(); + } +#ifdef QNATIVESOCKETENGINE_DEBUG + qDebug() << "QAsyncSelect::IssueRequest() - IsActive" << IsActive(); +#endif +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qsymbiansocketengine_p.h b/src/network/socket/qsymbiansocketengine_p.h new file mode 100644 index 0000000000..85ab54af12 --- /dev/null +++ b/src/network/socket/qsymbiansocketengine_p.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2010 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 QSYMBIANSOCKETENGINE_P_H +#define QSYMBIANSOCKETENGINE_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#include "qplatformdefs.h" + +#include <private/qeventdispatcher_symbian_p.h> +#include <unistd.h> +#include <es_sock.h> +#include <in_sock.h> + +QT_BEGIN_NAMESPACE + + +class QSymbianSocketEnginePrivate; +class QNetworkInterface; + +class Q_AUTOTEST_EXPORT QSymbianSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT + friend class QAsyncSelect; +public: + QSymbianSocketEngine(QObject *parent = 0); + ~QSymbianSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + QNetworkInterface multicastInterface() const; + bool setMulticastInterface(const QNetworkInterface &iface); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 bytesToWrite() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + + bool event(QEvent* ev); + + Q_INVOKABLE void startNotifications(); + +public Q_SLOTS: + // TODO: Why do we do this? This is private Qt implementation stuff anyway, no need for it + // non-virtual override; + void connectionNotification(); + +private: + Q_DECLARE_PRIVATE(QSymbianSocketEngine) + Q_DISABLE_COPY(QSymbianSocketEngine) +}; + +class QSocketNotifier; + +class QReadNotifier; +class QWriteNotifier; +class QExceptionNotifier; +class QAsyncSelect : public QActiveObject +{ +public: + QAsyncSelect(QEventDispatcherSymbian *dispatcher, RSocket& sock, QSymbianSocketEngine *parent); + ~QAsyncSelect(); + + void deleteLater(); + void IssueRequest(); + + void refresh(); + +protected: + void DoCancel(); + void RunL(); + void run(); + TInt RunError(TInt aError); + +private: + bool m_inSocketEvent; + bool m_deleteLater; + RSocket &m_socket; + + TUint m_selectFlags; + TPckgBuf<TUint> m_selectBuf; //in & out IPC buffer + QSymbianSocketEngine *engine; +}; + +class QSymbianSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QSymbianSocketEngine) +public: + QSymbianSocketEnginePrivate(); + ~QSymbianSocketEnginePrivate(); + + int socketDescriptor; + mutable RSocket nativeSocket; + // From QtCore: + RSocketServ& socketServer; + mutable RTimer selectTimer; + + bool readNotificationsEnabled; + bool writeNotificationsEnabled; + bool exceptNotificationsEnabled; + QAsyncSelect* asyncSelect; + + // FIXME this is duplicated from qnativesocketengine_p.h + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + //symbian specific + InvalidAddressErrorString, + SessionNotOpenErrorString, + + UnknownSocketErrorString = -1 + }; + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + void getPortAndAddress(const TInetAddr& a, quint16 *port, QHostAddress *addr); + void setPortAndAddress(TInetAddr& nativeAddr, quint16 port, const QHostAddress &addr); + void setError(TInt symbianError); + + int nativeSelect(int timeout, bool selectForRead) const; + int nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const; + + bool createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); + + bool multicastGroupMembershipHelper(const QHostAddress &groupAddress, + const QNetworkInterface &iface, + TUint operation); + static bool translateSocketOption(QAbstractSocketEngine::SocketOption opt, TUint &n, TUint &level); +}; + +QT_END_NAMESPACE + +#endif // QSYMBIANSOCKETENGINE_P_H diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp new file mode 100644 index 0000000000..5a60764a3b --- /dev/null +++ b/src/network/socket/qtcpserver.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** 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 QTCPSERVER_DEBUG + +/*! \class QTcpServer + + \brief The QTcpServer class provides a TCP-based server. + + \reentrant + \ingroup network + \inmodule QtNetwork + + This class makes it possible to accept incoming TCP connections. + You can specify the port or have QTcpServer pick one + automatically. You can listen on a specific address or on all the + machine's addresses. + + Call listen() to have the server listen for incoming connections. + The newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection as + a connected QTcpSocket. The function returns a pointer to a + QTcpSocket in QAbstractSocket::ConnectedState that you can use for + communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description of + what happened. + + When listening for connections, the address and port on which the + server is listening are available as serverAddress() and + serverPort(). + + Calling close() makes QTcpServer stop listening for incoming + connections. + + Although QTcpServer is mostly designed for use with an event + loop, it's possible to use it without one. In that case, you must + use waitForNewConnection(), which blocks until either a + connection is available or a timeout expires. + + \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, it will lead to a panic. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QTcpSocket, {Fortune Server Example}, {Threaded Fortune Server Example}, + {Loopback Example}, {Torrent Example} +*/ + +/*! \fn void QTcpServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +#include "private/qobject_p.h" +#include "qalgorithms.h" +#include "qhostaddress.h" +#include "qlist.h" +#include "qpointer.h" +#include "qabstractsocketengine_p.h" +#include "qtcpserver.h" +#include "qtcpsocket.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +class QTcpServerPrivate : public QObjectPrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QTcpServer) +public: + QTcpServerPrivate(); + ~QTcpServerPrivate(); + + QList<QTcpSocket *> pendingConnections; + + quint16 port; + QHostAddress address; + + QAbstractSocket::SocketState state; + QAbstractSocketEngine *socketEngine; + + QAbstractSocket::SocketError serverSocketError; + QString serverSocketErrorString; + + int maxConnections; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy resolveProxy(const QHostAddress &address, quint16 port); +#endif + + // from QAbstractSocketEngineReceiver + void readNotification(); + inline void writeNotification() {} + inline void exceptionNotification() {} + inline void connectionNotification() {} +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *) {} +#endif + +}; + +/*! \internal +*/ +QTcpServerPrivate::QTcpServerPrivate() + : port(0) + , state(QAbstractSocket::UnconnectedState) + , socketEngine(0) + , serverSocketError(QAbstractSocket::UnknownSocketError) + , maxConnections(30) +{ +} + +/*! \internal +*/ +QTcpServerPrivate::~QTcpServerPrivate() +{ +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +QNetworkProxy QTcpServerPrivate::resolveProxy(const QHostAddress &address, quint16 port) +{ + if (address == QHostAddress::LocalHost || + address == QHostAddress::LocalHostIPv6) + return QNetworkProxy::NoProxy; + + QList<QNetworkProxy> proxies; + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(port, QString(), QNetworkProxyQuery::TcpServer); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (p.capabilities() & QNetworkProxy::ListeningCapability) + return p; + } + + // no proxy found + // DefaultProxy will raise an error + return QNetworkProxy(QNetworkProxy::DefaultProxy); +} +#endif + +/*! \internal +*/ +void QTcpServerPrivate::readNotification() +{ + Q_Q(QTcpServer); + for (;;) { + if (pendingConnections.count() >= maxConnections) { +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() too many connections"); +#endif + if (socketEngine->isReadNotificationEnabled()) + socketEngine->setReadNotificationEnabled(false); + return; + } + + int descriptor = socketEngine->accept(); + if (descriptor == -1) + break; +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() accepted socket %i", descriptor); +#endif + q->incomingConnection(descriptor); + + QPointer<QTcpServer> that = q; + emit q->newConnection(); + if (!that || !q->isListening()) + return; + } +} + +/*! + Constructs a QTcpServer object. + + \a parent is passed to the QObject constructor. + + \sa listen(), setSocketDescriptor() +*/ +QTcpServer::QTcpServer(QObject *parent) + : QObject(*new QTcpServerPrivate, parent) +{ +} + +/*! + Destroys the QTcpServer object. If the server is listening for + connections, the socket is automatically closed. + + Any client \l{QTcpSocket}s that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() +*/ +QTcpServer::~QTcpServer() +{ + close(); +} + +/*! + Tells the server to listen for incoming connections on address \a + address and port \a port. If \a port is 0, a port is chosen + automatically. If \a address is QHostAddress::Any, the server + will listen on all network interfaces. + + Returns true on success; otherwise returns false. + + \sa isListening() +*/ +bool QTcpServer::listen(const QHostAddress &address, quint16 port) +{ + Q_D(QTcpServer); + if (d->state == QAbstractSocket::ListeningState) { + qWarning("QTcpServer::listen() called when already listening"); + return false; + } + + QAbstractSocket::NetworkLayerProtocol proto = address.protocol(); + +#ifdef QT_NO_NETWORKPROXY + static const QNetworkProxy &proxy = *(QNetworkProxy *)0; +#else + QNetworkProxy proxy = d->resolveProxy(address, port); +#endif + + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(QAbstractSocket::TcpSocket, proxy, this); + if (!d->socketEngine) { + d->serverSocketError = QAbstractSocket::UnsupportedSocketOperationError; + d->serverSocketErrorString = tr("Operation on socket is not supported"); + return false; + } +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine->initialize(QAbstractSocket::TcpSocket, proto)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + +#if defined(Q_OS_UNIX) + // Under Unix, we want to be able to bind to the port, even if a socket on + // the same address-port is in TIME_WAIT. Under Windows this is possible + // anyway -- furthermore, the meaning of reusable on Windows is different: + // it means that you can use the same address-port for multiple listening + // sockets. + // Don't abort though if we can't set that option. For example the socks + // engine doesn't support that option, but that shouldn't prevent us from + // trying to bind/listen. + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); +#endif + + if (!d->socketEngine->bind(address, port)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (!d->socketEngine->listen()) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = QAbstractSocket::ListeningState; + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::listen(%i, \"%s\") == true (listening on port %i)", port, + address.toString().toLatin1().constData(), d->socketEngine->localPort()); +#endif + return true; +} + +/*! + Returns true if the server is currently listening for incoming + connections; otherwise returns false. + + \sa listen() +*/ +bool QTcpServer::isListening() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(false); + return d->socketEngine->state() == QAbstractSocket::ListeningState; +} + +/*! + Closes the server. The server will no longer listen for incoming + connections. + + \sa listen() +*/ +void QTcpServer::close() +{ + Q_D(QTcpServer); + + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + + if (d->socketEngine) { + d->socketEngine->close(); + QT_TRY { + d->socketEngine->deleteLater(); + } QT_CATCH(const std::bad_alloc &) { + // in out of memory situations, the socketEngine + // will be deleted in ~QTcpServer (it's a child-object of this) + } + d->socketEngine = 0; + } + + d->state = QAbstractSocket::UnconnectedState; +} + +/*! + Returns the native socket descriptor the server uses to listen + for incoming instructions, or -1 if the server is not listening. + + If the server is using QNetworkProxy, the returned descriptor may + not be usable with native socket functions. + + \sa setSocketDescriptor(), isListening() +*/ +int QTcpServer::socketDescriptor() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(-1); + return d->socketEngine->socketDescriptor(); +} + +/*! + Sets the socket descriptor this server should use when listening + for incoming connections to \a socketDescriptor. Returns true if + the socket is set successfully; otherwise returns false. + + The socket is assumed to be in listening state. + + \sa socketDescriptor(), isListening() +*/ +bool QTcpServer::setSocketDescriptor(int socketDescriptor) +{ + Q_D(QTcpServer); + if (isListening()) { + qWarning("QTcpServer::setSocketDescriptor() called when already listening"); + return false; + } + + if (d->socketEngine) + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the socket engine (if it has been set) + d->socketEngine->setProperty("_q_networksession", property("_q_networksession")); +#endif + if (!d->socketEngine->initialize(socketDescriptor, QAbstractSocket::ListeningState)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) failed (%s)", socketDescriptor, + d->serverSocketErrorString.toLatin1().constData()); +#endif + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = d->socketEngine->state(); + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) succeeded.", socketDescriptor); +#endif + return true; +} + +/*! + Returns the server's port if the server is listening for + connections; otherwise returns 0. + + \sa serverAddress(), listen() +*/ +quint16 QTcpServer::serverPort() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(0); + return d->socketEngine->localPort(); +} + +/*! + Returns the server's address if the server is listening for + connections; otherwise returns QHostAddress::Null. + + \sa serverPort(), listen() +*/ +QHostAddress QTcpServer::serverAddress() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(QHostAddress(QHostAddress::Null)); + return d->socketEngine->localAddress(); +} + +/*! + Waits for at most \a msec milliseconds or until an incoming + connection is available. Returns true if a connection is + available; otherwise returns false. If the operation timed out + and \a timedOut is not 0, *\a timedOut will be set to true. + + This is a blocking function call. Its use is disadvised in a + single-threaded GUI application, since the whole application will + stop responding until the function returns. + waitForNewConnection() is mostly useful when there is no event + loop available. + + The non-blocking alternative is to connect to the newConnection() + signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() +*/ +bool QTcpServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QTcpServer); + if (d->state != QAbstractSocket::ListeningState) + return false; + + if (!d->socketEngine->waitForRead(msec, timedOut)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (timedOut && *timedOut) + return false; + + d->readNotification(); + + return true; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() +*/ +bool QTcpServer::hasPendingConnections() const +{ + return !d_func()->pendingConnections.isEmpty(); +} + +/*! + Returns the next pending connection as a connected QTcpSocket + object. + + The socket is created as a child of the server, which means that + it is automatically deleted when the QTcpServer object is + destroyed. It is still a good idea to delete the object + explicitly when you are done with it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \note The returned QTcpSocket object cannot be used from another + thread. If you want to use an incoming connection from another thread, + you need to override incomingConnection(). + + \sa hasPendingConnections() +*/ +QTcpSocket *QTcpServer::nextPendingConnection() +{ + Q_D(QTcpServer); + if (d->pendingConnections.isEmpty()) + return 0; + + if (!d->socketEngine->isReadNotificationEnabled()) + d->socketEngine->setReadNotificationEnabled(true); + + return d->pendingConnections.takeFirst(); +} + +/*! + This virtual function is called by QTcpServer when a new + connection is available. The \a socketDescriptor argument is the + native socket descriptor for the accepted connection. + + The base implementation creates a QTcpSocket, sets the socket + descriptor and then stores the QTcpSocket in an internal list of + pending connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior when a + connection is available. + + If this server is using QNetworkProxy then the \a socketDescriptor + may not be usable with native socket functions, and should only be + used with QTcpSocket::setSocketDescriptor(). + + \note If you want to handle an incoming connection as a new QTcpSocket + object in another thread you have to pass the socketDescriptor + to the other thread and create the QTcpSocket object there and + use its setSocketDescriptor() method. + + \sa newConnection(), nextPendingConnection(), addPendingConnection() +*/ +void QTcpServer::incomingConnection(int socketDescriptor) +{ +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor); +#endif + + QTcpSocket *socket = new QTcpSocket(this); + socket->setSocketDescriptor(socketDescriptor); + addPendingConnection(socket); +} + +/*! + This function is called by QTcpServer::incomingConnection() + to add the \a socket to the list of pending incoming connections. + + \note Don't forget to call this member from reimplemented + incomingConnection() if you do not want to break the + Pending Connections mechanism. + + \sa incomingConnection() + \since 4.7 +*/ +void QTcpServer::addPendingConnection(QTcpSocket* socket) +{ + d_func()->pendingConnections.append(socket); +} + +/*! + Sets the maximum number of pending accepted connections to \a + numConnections. QTcpServer will accept no more than \a + numConnections incoming connections before + nextPendingConnection() is called. By default, the limit is 30 + pending connections. + + Clients may still able to connect after the server has reached + its maximum number of pending connections (i.e., QTcpSocket can + still emit the connected() signal). QTcpServer will stop + accepting the new connections, but the operating system may + still keep them in queue. + + \sa maxPendingConnections(), hasPendingConnections() +*/ +void QTcpServer::setMaxPendingConnections(int numConnections) +{ + d_func()->maxConnections = numConnections; +} + +/*! + Returns the maximum number of pending accepted connections. The + default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() +*/ +int QTcpServer::maxPendingConnections() const +{ + return d_func()->maxConnections; +} + +/*! + Returns an error code for the last error that occurred. + + \sa errorString() +*/ +QAbstractSocket::SocketError QTcpServer::serverError() const +{ + return d_func()->serverSocketError; +} + +/*! + Returns a human readable description of the last error that + occurred. + + \sa serverError() +*/ +QString QTcpServer::errorString() const +{ + return d_func()->serverSocketErrorString; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qtcpserver.cpp 0 + + \sa proxy(), QNetworkProxy +*/ +void QTcpServer::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QTcpServer); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used. + + \sa setProxy(), QNetworkProxy +*/ +QNetworkProxy QTcpServer::proxy() const +{ + Q_D(const QTcpServer); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +QT_END_NAMESPACE + +#include "moc_qtcpserver.cpp" + diff --git a/src/network/socket/qtcpserver.h b/src/network/socket/qtcpserver.h new file mode 100644 index 0000000000..4018da6d00 --- /dev/null +++ b/src/network/socket/qtcpserver.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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 QTCPSERVER_H +#define QTCPSERVER_H + +#include <QtCore/qobject.h> +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpServerPrivate; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QTcpSocket; + +class Q_NETWORK_EXPORT QTcpServer : public QObject +{ + Q_OBJECT +public: + explicit QTcpServer(QObject *parent = 0); + virtual ~QTcpServer(); + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor); + + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + virtual bool hasPendingConnections() const; + virtual QTcpSocket *nextPendingConnection(); + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +protected: + virtual void incomingConnection(int handle); + void addPendingConnection(QTcpSocket* socket); + +Q_SIGNALS: + void newConnection(); + +private: + Q_DISABLE_COPY(QTcpServer) + Q_DECLARE_PRIVATE(QTcpServer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSERVER_H diff --git a/src/network/socket/qtcpsocket.cpp b/src/network/socket/qtcpsocket.cpp new file mode 100644 index 0000000000..32edc2f8ab --- /dev/null +++ b/src/network/socket/qtcpsocket.cpp @@ -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$ +** +****************************************************************************/ + +//#define QTCPSOCKET_DEBUG + +/*! + \class QTcpSocket + + \brief The QTcpSocket class provides a TCP socket. + + \reentrant + \ingroup network + \inmodule QtNetwork + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. It is + especially well suited for continuous transmission of data. + + QTcpSocket is a convenience subclass of QAbstractSocket that + allows you to establish a TCP connection and transfer streams of + data. See the QAbstractSocket documentation for details. + + \bold{Note:} TCP sockets cannot be opened in QIODevice::Unbuffered mode. + + \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, it will result in a panic. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QTcpServer, QUdpSocket, QFtp, QNetworkAccessManager, + {Fortune Server Example}, {Fortune Client Example}, + {Threaded Fortune Server Example}, {Blocking Fortune Client Example}, + {Loopback Example}, {Torrent Example} +*/ + +#include "qlist.h" +#include "qtcpsocket_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" + +QT_BEGIN_NAMESPACE + +/*! + Creates a QTcpSocket object in state \c UnconnectedState. + + \a parent is passed on to the QObject constructor. + + \sa socketType() +*/ +QTcpSocket::QTcpSocket(QObject *parent) + : QAbstractSocket(TcpSocket, *new QTcpSocketPrivate, parent) +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::QTcpSocket()"); +#endif + d_func()->isBuffered = true; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ + +QTcpSocket::~QTcpSocket() +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::~QTcpSocket()"); +#endif +} + +/*! + \internal +*/ +QTcpSocket::QTcpSocket(QTcpSocketPrivate &dd, QObject *parent) + : QAbstractSocket(TcpSocket, dd, parent) +{ + d_func()->isBuffered = true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qtcpsocket.h b/src/network/socket/qtcpsocket.h new file mode 100644 index 0000000000..a50e0feca9 --- /dev/null +++ b/src/network/socket/qtcpsocket.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 QTCPSOCKET_H +#define QTCPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpSocketPrivate; + +class Q_NETWORK_EXPORT QTcpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + explicit QTcpSocket(QObject *parent = 0); + virtual ~QTcpSocket(); + +protected: + QTcpSocket(QTcpSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DISABLE_COPY(QTcpSocket) + Q_DECLARE_PRIVATE(QTcpSocket) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSOCKET_H diff --git a/src/network/socket/qtcpsocket_p.h b/src/network/socket/qtcpsocket_p.h new file mode 100644 index 0000000000..12414df2ed --- /dev/null +++ b/src/network/socket/qtcpsocket_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QTCPSOCKET_P_H +#define QTCPSOCKET_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/qtcpsocket.h> +#include <private/qabstractsocket_p.h> + +QT_BEGIN_NAMESPACE + +class QTcpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QTcpSocket) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp new file mode 100644 index 0000000000..f8bcd1b967 --- /dev/null +++ b/src/network/socket/qudpsocket.cpp @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** 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 QUDPSOCKET_DEBUG + +/*! \class QUdpSocket + + \reentrant + \brief The QUdpSocket class provides a UDP socket. + + \ingroup network + \inmodule QtNetwork + + UDP (User Datagram Protocol) is a lightweight, unreliable, + datagram-oriented, connectionless protocol. It can be used when + reliability isn't important. QUdpSocket is a subclass of + QAbstractSocket that allows you to send and receive UDP + datagrams. + + The most common way to use this class is to bind to an address and port + using bind(), then call writeDatagram() and readDatagram() to transfer + data. If you want to use the standard QIODevice functions read(), + readLine(), write(), etc., you must first connect the socket directly to a + peer by calling connectToHost(). + + The socket emits the bytesWritten() signal every time a datagram + is written to the network. If you just want to send datagrams, + you don't need to call bind(). + + The readyRead() signal is emitted whenever datagrams arrive. In + that case, hasPendingDatagrams() returns true. Call + pendingDatagramSize() to obtain the size of the first pending + datagram, and readDatagram() to read it. + + \note An incoming datagram should be read when you receive the readyRead() + signal, otherwise this signal will not be emitted for the next datagram. + + Example: + + \snippet doc/src/snippets/code/src_network_socket_qudpsocket.cpp 0 + + QUdpSocket also supports UDP multicast. Use joinMulticastGroup() and + leaveMulticastGroup() to control group membership, and + QAbstractSocket::MulticastTtlOption and + QAbstractSocket::MulticastLoopbackOption to set the TTL and loopback socket + options. Use setMulticastInterface() to control the outgoing interface for + multicast datagrams, and multicastInterface() to query it. + + With QUdpSocket, you can also establish a virtual connection to a + UDP server using connectToHost() and then use read() and write() + to exchange datagrams without specifying the receiver for each + datagram. + + The \l{network/broadcastsender}{Broadcast Sender}, + \l{network/broadcastreceiver}{Broadcast Receiver}, + \l{network/multicastsender}{Multicast Sender}, and + \l{network/multicastreceiver}{Multicast Receiver} examples illustrate how + to use QUdpSocket in applications. + + \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 QTcpSocket +*/ + +/*! \enum QUdpSocket::BindFlag + \since 4.1 + + This enum describes the different flags you can pass to modify the + behavior of QUdpSocket::bind(). + + \note On Symbian OS bind flags behaviour depends on process capabilties. + If process has NetworkControl capability, the bind attempt with + ReuseAddressHint will always succeed even if the address and port is already + bound by another socket with any flags. If process does not have + NetworkControl capability, the bind attempt to address and port already + bound by another socket will always fail. + + \value ShareAddress Allow other services to bind to the same address + and port. This is useful when multiple processes share + the load of a single service by listening to the same address and port + (e.g., a web server with several pre-forked listeners can greatly + improve response time). However, because any service is allowed to + rebind, this option is subject to certain security considerations. + Note that by combining this option with ReuseAddressHint, you will + also allow your service to rebind an existing shared address. On + Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows, + this option is ignored. + + \value DontShareAddress Bind the address and port exclusively, so that + no other services are allowed to rebind. By passing this option to + QUdpSocket::bind(), you are guaranteed that on successs, your service + is the only one that listens to the address and port. No services are + allowed to rebind, even if they pass ReuseAddressHint. This option + provides more security than ShareAddress, but on certain operating + systems, it requires you to run the server with administrator privileges. + On Unix and Mac OS X, not sharing is the default behavior for binding + an address and port, so this option is ignored. On Windows, this + option uses the SO_EXCLUSIVEADDRUSE socket option. + + \value ReuseAddressHint Provides a hint to QUdpSocket that it should try + to rebind the service even if the address and port are already bound by + another socket. On Windows, this is equivalent to the SO_REUSEADDR + socket option. On Unix, this option is ignored. + + \value DefaultForPlatform The default option for the current platform. + On Unix and Mac OS X, this is equivalent to (DontShareAddress + + ReuseAddressHint), and on Windows, its equivalent to ShareAddress. +*/ + +#include "qhostaddress.h" +#include "qnetworkinterface.h" +#include "qabstractsocket_p.h" +#include "qudpsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_UDPSOCKET + +#define QT_CHECK_BOUND(function, a) do { \ + if (!isValid()) { \ + qWarning(function" called on a QUdpSocket when not in QUdpSocket::BoundState"); \ + return (a); \ + } } while (0) + +class QUdpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QUdpSocket) + + bool doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress); +public: + inline bool ensureInitialized(const QHostAddress &bindAddress, quint16 bindPort) + { return doEnsureInitialized(bindAddress, bindPort, QHostAddress()); } + + inline bool ensureInitialized(const QHostAddress &remoteAddress) + { return doEnsureInitialized(QHostAddress(), 0, remoteAddress); } +}; + +bool QUdpSocketPrivate::doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress) +{ + const QHostAddress *address = &bindAddress; + QAbstractSocket::NetworkLayerProtocol proto = address->protocol(); + if (proto == QUdpSocket::UnknownNetworkLayerProtocol) { + address = &remoteAddress; + proto = address->protocol(); + } + +#if defined(QT_NO_IPV6) + Q_Q(QUdpSocket); + if (proto == QUdpSocket::IPv6Protocol) { + socketError = QUdpSocket::UnsupportedSocketOperationError; + q->setErrorString(QUdpSocket::tr("This platform does not support IPv6")); + return false; + } +#endif + + // now check if the socket engine is initialized and to the right type + if (!socketEngine || !socketEngine->isValid()) { + resolveProxy(remoteAddress.toString(), bindPort); + if (!initSocketLayer(address->protocol())) + return false; + } + + return true; +} + +/*! + Creates a QUdpSocket object. + + \a parent is passed to the QObject constructor. + + \sa socketType() +*/ +QUdpSocket::QUdpSocket(QObject *parent) + : QAbstractSocket(UdpSocket, *new QUdpSocketPrivate, parent) +{ + d_func()->isBuffered = false; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ +QUdpSocket::~QUdpSocket() +{ +} + +/*! + Binds this socket to the address \a address and the port \a port. + When bound, the signal readyRead() is emitted whenever a UDP + datagram arrives on the specified address and port. This function + is useful to write UDP servers. + + On success, the functions returns true and the socket enters + BoundState; otherwise it returns false. + + The socket is bound using the DefaultForPlatform BindMode. + + \sa readDatagram() +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! + \since 4.1 + \overload + + Binds to \a address on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + +#ifdef Q_OS_UNIX + if ((mode & ShareAddress) || (mode & ReuseAddressHint)) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); +#endif +#ifdef Q_OS_WIN + if (mode & ReuseAddressHint) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); + if (mode & DontShareAddress) + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); +#endif + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! \overload + + Binds to QHostAddress:Any on port \a port. +*/ +bool QUdpSocket::bind(quint16 port) +{ + return bind(QHostAddress::Any, port); +} + +/*! + \since 4.1 + \overload + + Binds to QHostAddress:Any on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(quint16 port, BindMode mode) +{ + return bind(QHostAddress::Any, port, mode); +} + +#ifndef QT_NO_NETWORKINTERFACE + +/*! + \since 4.8 + + Joins the the multicast group specified by \a groupAddress on the default + interface chosen by the operating system. The socket must be in BoundState, + otherwise an error occurs. + + This function returns true if successful; otherwise it returns false + and sets the socket error accordingly. + + \sa leaveMulticastGroup() +*/ +bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress) +{ + return joinMulticastGroup(groupAddress, QNetworkInterface()); +} + +/*! + \since 4.8 + \overload + + Joins the multicast group address \a groupAddress on the interface \a + iface. + + \sa leaveMulticastGroup() +*/ +bool QUdpSocket::joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + Q_D(QUdpSocket); + QT_CHECK_BOUND("QUdpSocket::joinMulticastGroup()", false); + return d->socketEngine->joinMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 + + Leaves the multicast group specified by \a groupAddress on the default + interface chosen by the operating system. The socket must be in BoundState, + otherwise an error occurs. + + This function returns true if successful; otherwise it returns false and + sets the socket error accordingly. + + \sa joinMulticastGroup() +*/ +bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress) +{ + return leaveMulticastGroup(groupAddress, QNetworkInterface()); +} + +/*! + \since 4.8 + \overload + + Leaves the multicast group specified by \a groupAddress on the interface \a + iface. + + \sa joinMulticastGroup() +*/ +bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface) +{ + QT_CHECK_BOUND("QUdpSocket::leaveMulticastGroup()", false); + return d_func()->socketEngine->leaveMulticastGroup(groupAddress, iface); +} + +/*! + \since 4.8 + + Returns the interface for the outgoing interface for multicast datagrams. + This corresponds to the IP_MULTICAST_IF socket option for IPv4 sockets and + the IPV6_MULTICAST_IF socket option for IPv6 sockets. If no interface has + been previously set, this function returns an invalid QNetworkInterface. + The socket must be in BoundState, otherwise an invalid QNetworkInterface is + returned. + + \sa setMulticastInterface() +*/ +QNetworkInterface QUdpSocket::multicastInterface() const +{ + Q_D(const QUdpSocket); + QT_CHECK_BOUND("QUdpSocket::multicastInterface()", QNetworkInterface()); + return d->socketEngine->multicastInterface(); +} + +/*! + \since 4.8 + + Sets the outgoing interface for multicast datagrams to the interface \a + iface. This corresponds to the IP_MULTICAST_IF socket option for IPv4 + sockets and the IPV6_MULTICAST_IF socket option for IPv6 sockets. The + socket must be in BoundState, otherwise this function does nothing. + + \sa multicastInterface(), joinMulticastGroup(), leaveMulticastGroup() +*/ +void QUdpSocket::setMulticastInterface(const QNetworkInterface &iface) +{ + Q_D(QUdpSocket); + if (!isValid()) { + qWarning("QUdpSocket::setMulticastInterface() called on a QUdpSocket when not in QUdpSocket::BoundState"); + return; + } + d->socketEngine->setMulticastInterface(iface); +} + +#endif // QT_NO_NETWORKINTERFACE + +/*! + Returns true if at least one datagram is waiting to be read; + otherwise returns false. + + \sa pendingDatagramSize(), readDatagram() +*/ +bool QUdpSocket::hasPendingDatagrams() const +{ + QT_CHECK_BOUND("QUdpSocket::hasPendingDatagrams()", false); + return d_func()->socketEngine->hasPendingDatagrams(); +} + +/*! + Returns the size of the first pending UDP datagram. If there is + no datagram available, this function returns -1. + + \sa hasPendingDatagrams(), readDatagram() +*/ +qint64 QUdpSocket::pendingDatagramSize() const +{ + QT_CHECK_BOUND("QUdpSocket::pendingDatagramSize()", -1); + return d_func()->socketEngine->pendingDatagramSize(); +} + +/*! + Sends the datagram at \a data of size \a size to the host + address \a address at port \a port. Returns the number of + bytes sent on success; otherwise returns -1. + + Datagrams are always written as one block. The maximum size of a + datagram is highly platform-dependent, but can be as low as 8192 + bytes. If the datagram is too large, this function will return -1 + and error() will return DatagramTooLargeError. + + Sending datagrams larger than 512 bytes is in general disadvised, + as even if they are sent successfully, they are likely to be + fragmented by the IP layer before arriving at their final + destination. + + \warning In S60 5.0 and earlier versions, the writeDatagram return + value is not reliable for large datagrams. + + \warning Calling this function on a connected UDP socket may + result in an error and no packet being sent. If you are using a + connected socket, use write() to send datagrams. + + \sa readDatagram(), write() +*/ +qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, + quint16 port) +{ + Q_D(QUdpSocket); +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::writeDatagram(%p, %llu, \"%s\", %i)", data, size, + address.toString().toLatin1().constData(), port); +#endif + if (!d->ensureInitialized(address)) + return -1; + + qint64 sent = d->socketEngine->writeDatagram(data, size, address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (sent >= 0) { + emit bytesWritten(sent); + } else { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return sent; +} + +/*! + \fn qint64 QUdpSocket::writeDatagram(const QByteArray &datagram, + const QHostAddress &host, quint16 port) + \overload + + Sends the datagram \a datagram to the host address \a host and at + port \a port. +*/ + +/*! + Receives a datagram no larger than \a maxSize bytes and stores + it in \a data. The sender's host address and port is stored in + *\a address and *\a port (unless the pointers are 0). + + Returns the size of the datagram on success; otherwise returns + -1. + + If \a maxSize is too small, the rest of the datagram will be + lost. To avoid loss of data, call pendingDatagramSize() to + determine the size of the pending datagram before attempting to + read it. If \a maxSize is 0, the datagram will be discarded. + + \sa writeDatagram(), hasPendingDatagrams(), pendingDatagramSize() +*/ +qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QUdpSocket); + +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::readDatagram(%p, %llu, %p, %p)", data, maxSize, address, port); +#endif + QT_CHECK_BOUND("QUdpSocket::readDatagram()", -1); + qint64 readBytes = d->socketEngine->readDatagram(data, maxSize, address, port); + d_func()->socketEngine->setReadNotificationEnabled(true); + if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return readBytes; +} +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE diff --git a/src/network/socket/qudpsocket.h b/src/network/socket/qudpsocket.h new file mode 100644 index 0000000000..7502349c7a --- /dev/null +++ b/src/network/socket/qudpsocket.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 QUDPSOCKET_H +#define QUDPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_UDPSOCKET + +class QNetworkInterface; +class QUdpSocketPrivate; + +class Q_NETWORK_EXPORT QUdpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + enum BindFlag { + DefaultForPlatform = 0x0, + ShareAddress = 0x1, + DontShareAddress = 0x2, + ReuseAddressHint = 0x4 + }; + Q_DECLARE_FLAGS(BindMode, BindFlag) + + explicit QUdpSocket(QObject *parent = 0); + virtual ~QUdpSocket(); + + bool bind(const QHostAddress &address, quint16 port); + bool bind(quint16 port = 0); + bool bind(const QHostAddress &address, quint16 port, BindMode mode); + bool bind(quint16 port, BindMode mode); + // ### Qt 5: Merge the bind functions + +#ifndef QT_NO_NETWORKINTERFACE + bool joinMulticastGroup(const QHostAddress &groupAddress); + bool joinMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + bool leaveMulticastGroup(const QHostAddress &groupAddress); + bool leaveMulticastGroup(const QHostAddress &groupAddress, + const QNetworkInterface &iface); + + QNetworkInterface multicastInterface() const; + void setMulticastInterface(const QNetworkInterface &iface); +#endif + + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = 0, quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port); + inline qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port) + { return writeDatagram(datagram.constData(), datagram.size(), host, port); } + +private: + Q_DISABLE_COPY(QUdpSocket) + Q_DECLARE_PRIVATE(QUdpSocket) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QUdpSocket::BindMode) + +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QUDPSOCKET_H diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri new file mode 100644 index 0000000000..ac9001247a --- /dev/null +++ b/src/network/socket/socket.pri @@ -0,0 +1,70 @@ +# Qt network socket + +HEADERS += socket/qabstractsocketengine_p.h \ + socket/qhttpsocketengine_p.h \ + socket/qsocks5socketengine_p.h \ + socket/qabstractsocket.h \ + socket/qabstractsocket_p.h \ + socket/qtcpsocket.h \ + socket/qudpsocket.h \ + socket/qtcpserver.h \ + socket/qlocalserver.h \ + socket/qlocalserver_p.h \ + socket/qlocalsocket.h \ + socket/qlocalsocket_p.h + +SOURCES += socket/qabstractsocketengine.cpp \ + socket/qhttpsocketengine.cpp \ + socket/qsocks5socketengine.cpp \ + socket/qabstractsocket.cpp \ + socket/qtcpsocket.cpp \ + socket/qudpsocket.cpp \ + socket/qtcpserver.cpp \ + socket/qlocalsocket.cpp \ + socket/qlocalserver.cpp + +# On Symbian we use QSymbianSocketEngine +symbian:SOURCES += socket/qsymbiansocketengine.cpp +symbian:HEADERS += socket/qsymbiansocketengine_p.h +# On others we use QNativeSocketEngine +!symbian:SOURCES += socket/qnativesocketengine.cpp +!symbian:HEADERS += socket/qnativesocketengine_p.h + +unix:!symbian: { + SOURCES += socket/qnativesocketengine_unix.cpp \ + socket/qlocalsocket_unix.cpp \ + socket/qlocalserver_unix.cpp +} + +symbian: { + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} + +unix:HEADERS += \ + socket/qnet_unix_p.h + +win32:SOURCES += socket/qnativesocketengine_win.cpp \ + socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + +wince*: { + SOURCES -= socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} + +integrity: { + SOURCES -= socket/qlocalsocket_unix.cpp \ + socket/qlocalserver_unix.cpp + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp \ + socket/qnativesocketengine_unix.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} diff --git a/src/network/ssl/qssl.cpp b/src/network/ssl/qssl.cpp new file mode 100644 index 0000000000..55942969e4 --- /dev/null +++ b/src/network/ssl/qssl.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** 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 "qsslkey.h" + +QT_BEGIN_NAMESPACE + +/*! \namespace QSsl + + \brief The QSsl namespace declares enums common to all SSL classes in QtNetwork. + \since 4.3 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork +*/ + +/*! + \enum QSsl::KeyType + + Describes the two types of keys QSslKey supports. + + \value PrivateKey A private key. + \value PublicKey A public key. +*/ + +/*! + \enum QSsl::KeyAlgorithm + + Describes the different key algorithms supported by QSslKey. + + \value Rsa The RSA algorithm. + \value Dsa The DSA algorithm. +*/ + +/*! + \enum QSsl::EncodingFormat + + Describes supported encoding formats for certificates and keys. + + \value Pem The PEM format. + \value Der The DER format. +*/ + +/*! + \enum QSsl::AlternateNameEntryType + + Describes the key types for alternate name entries in QSslCertificate. + + \value EmailEntry An email entry; the entry contains an email address that + the certificate is valid for. + + \value DnsEntry A DNS host name entry; the entry contains a host name + entry that the certificate is valid for. The entry may contain wildcards. + + \sa QSslCertificate::alternateSubjectNames() + +*/ + +/*! + \enum QSsl::SslProtocol + + Describes the protocol of the cipher. + + \value SslV3 SSLv3 + \value SslV2 SSLv2 + \value TlsV1 TLSv1 + \value UnknownProtocol The cipher's protocol cannot be determined. + \value AnyProtocol The socket understands SSLv2, SSLv3, and TLSv1. This + value is used by QSslSocket only. + \value TlsV1SslV3 On the client side, this will send + a TLS 1.0 Client Hello, enabling TLSv1 and SSLv3 connections. + On the server side, this will enable both SSLv3 and TLSv1 connections. + \value SecureProtocols The default option, using protocols known to be secure; + currently behaves like TlsV1SslV3. + + Note: most servers using SSL understand both versions (2 and 3), + but it is recommended to use the latest version only for security + reasons. However, SSL and TLS are not compatible with each other: + if you get unexpected handshake failures, verify that you chose + the correct setting for your protocol. +*/ + +QT_END_NAMESPACE diff --git a/src/network/ssl/qssl.h b/src/network/ssl/qssl.h new file mode 100644 index 0000000000..24dbb09747 --- /dev/null +++ b/src/network/ssl/qssl.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 QSSL_H +#define QSSL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +namespace QSsl { + enum KeyType { + PrivateKey, + PublicKey + }; + + enum EncodingFormat { + Pem, + Der + }; + + enum KeyAlgorithm { + Rsa, + Dsa + }; + + enum AlternateNameEntryType { + EmailEntry, + DnsEntry + }; + + enum SslProtocol { + SslV3, + SslV2, + TlsV1, // ### Qt 5: rename to TlsV1_0 or so + AnyProtocol, + TlsV1SslV3, + SecureProtocols, + UnknownProtocol = -1 + }; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSSL_H diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp new file mode 100644 index 0000000000..a5cdf011aa --- /dev/null +++ b/src/network/ssl/qsslcertificate.cpp @@ -0,0 +1,858 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +/*! + \class QSslCertificate + \brief The QSslCertificate class provides a convenient API for an X509 certificate. + \since 4.3 + + \reentrant + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + QSslCertificate stores an X509 certificate, and is commonly used + to verify the identity and store information about the local host, + a remotely connected peer, or a trusted third party Certificate + Authority. + + There are many ways to construct a QSslCertificate. The most + common way is to call QSslSocket::peerCertificate(), which returns + a QSslCertificate object, or QSslSocket::peerCertificateChain(), + which returns a list of them. You can also load certificates from + a DER (binary) or PEM (Base64) encoded bundle, typically stored as + one or more local files, or in a Qt Resource. + + You can call isNull() to check if your certificate is null. By + default, QSslCertificate constructs a null certificate. To check + if the certificate is valid, call isValid(). A null certificate is + invalid, but an invalid certificate is not necessarily null. If + you want to reset all contents in a certificate, call clear(). + + After loading a certificate, you can find information about the + certificate, its subject, and its issuer, by calling one of the + many accessor functions, including version(), serialNumber(), + issuerInfo() and subjectInfo(). You can call effectiveDate() and + expiryDate() to check when the certificate starts being + effective and when it expires. + The publicKey() function returns the certificate + subject's public key as a QSslKey. You can call issuerInfo() or + subjectInfo() to get detailed information about the certificate + issuer and its subject. + + Internally, QSslCertificate is stored as an X509 structure. You + can access this handle by calling handle(), but the results are + likely to not be portable. + + \sa QSslSocket, QSslKey, QSslCipher, QSslError +*/ + +/*! + \enum QSslCertificate::SubjectInfo + + Describes keys that you can pass to QSslCertificate::issuerInfo() or + QSslCertificate::subjectInfo() to get information about the certificate + issuer or subject. + + \value Organization "O" The name of the organization. + + \value CommonName "CN" The common name; most often this is used to store + the host name. + + \value LocalityName "L" The locality. + + \value OrganizationalUnitName "OU" The organizational unit name. + + \value CountryName "C" The country. + + \value StateOrProvinceName "ST" The state or province. +*/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslcertificate.h" +#include "qsslcertificate_p.h" +#include "qsslkey.h" +#include "qsslkey_p.h" + +#include <QtCore/qatomic.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtCore/qdiriterator.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qmap.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE + +// forward declaration +static QMap<QString, QString> _q_mapFromOnelineName(char *name); + +/*! + Constructs a QSslCertificate by reading \a format encoded data + from \a device and using the first certificate found. You can + later call isNull() to see if \a device contained a certificate, + and if this certificate was loaded successfully. +*/ +QSslCertificate::QSslCertificate(QIODevice *device, QSsl::EncodingFormat format) + : d(new QSslCertificatePrivate) +{ + QSslSocketPrivate::ensureInitialized(); + if (device) + d->init(device->readAll(), format); +} + +/*! + Constructs a QSslCertificate by parsing the \a format encoded + \a data and using the first available certificate found. You can + later call isNull() to see if \a data contained a certificate, + and if this certificate was loaded successfully. +*/ +QSslCertificate::QSslCertificate(const QByteArray &data, QSsl::EncodingFormat format) + : d(new QSslCertificatePrivate) +{ + QSslSocketPrivate::ensureInitialized(); + d->init(data, format); +} + +/*! + Constructs an identical copy of \a other. +*/ +QSslCertificate::QSslCertificate(const QSslCertificate &other) : d(other.d) +{ +} + +/*! + Destroys the QSslCertificate. +*/ +QSslCertificate::~QSslCertificate() +{ +} + +/*! + Copies the contents of \a other into this certificate, making the two + certificates identical. +*/ +QSslCertificate &QSslCertificate::operator=(const QSslCertificate &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this certificate is the same as \a other; otherwise + returns false. +*/ +bool QSslCertificate::operator==(const QSslCertificate &other) const +{ + if (d == other.d) + return true; + if (d->null && other.d->null) + return true; + if (d->x509 && other.d->x509) + return q_X509_cmp(d->x509, other.d->x509) == 0; + return false; +} + +/*! + \fn bool QSslCertificate::operator!=(const QSslCertificate &other) const + + Returns true if this certificate is not the same as \a other; otherwise + returns false. +*/ + +/*! + Returns true if this is a null certificate (i.e., a certificate + with no contents); otherwise returns false. + + By default, QSslCertificate constructs a null certificate. + + \sa isValid(), clear() +*/ +bool QSslCertificate::isNull() const +{ + return d->null; +} + +/*! + Returns true if this certificate is valid; otherwise returns + false. + + Note: Currently, this function checks that the current + data-time is within the date-time range during which the + certificate is considered valid, and checks that the + certificate is not in a blacklist of fraudulent certificates. + + \sa isNull() +*/ +bool QSslCertificate::isValid() const +{ + const QDateTime currentTime = QDateTime::currentDateTime(); + return currentTime >= d->notValidBefore && + currentTime <= d->notValidAfter && + ! QSslCertificatePrivate::isBlacklisted(*this); +} + +/*! + Clears the contents of this certificate, making it a null + certificate. + + \sa isNull() +*/ +void QSslCertificate::clear() +{ + if (isNull()) + return; + d = new QSslCertificatePrivate; +} + +/*! + Returns the certificate's version string. +*/ +QByteArray QSslCertificate::version() const +{ + if (d->versionString.isEmpty() && d->x509) + d->versionString = + QByteArray::number(qlonglong(q_ASN1_INTEGER_get(d->x509->cert_info->version)) + 1); + + return d->versionString; +} + +/*! + Returns the certificate's serial number string in decimal format. + In case the serial number cannot be converted to decimal format + (i.e. if it is bigger than 4294967295, which means it does not fit into 4 bytes), + its hexadecimal version is returned. +*/ +QByteArray QSslCertificate::serialNumber() const +{ + if (d->serialNumberString.isEmpty() && d->x509) { + ASN1_INTEGER *serialNumber = d->x509->cert_info->serialNumber; + // if we cannot convert to a long, just output the hexadecimal number + if (serialNumber->length > 4) { + QByteArray hexString; + hexString.reserve(serialNumber->length * 3); + for (int a = 0; a < serialNumber->length; ++a) { + hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0'); + hexString += ':'; + } + hexString.chop(1); + d->serialNumberString = hexString; + } else { + d->serialNumberString = QByteArray::number(qlonglong(q_ASN1_INTEGER_get(serialNumber))); + } + } + return d->serialNumberString; +} + +/*! + Returns a cryptographic digest of this certificate. By default, + an MD5 digest will be generated, but you can also specify a + custom \a algorithm. +*/ +QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) const +{ + return QCryptographicHash::hash(toDer(), algorithm); +} + +static QString _q_SubjectInfoToString(QSslCertificate::SubjectInfo info) +{ + QString str; + switch (info) { + case QSslCertificate::Organization: str = QLatin1String("O"); break; + case QSslCertificate::CommonName: str = QLatin1String("CN"); break; + case QSslCertificate::LocalityName: str = QLatin1String("L"); break; + case QSslCertificate::OrganizationalUnitName: str = QLatin1String("OU"); break; + case QSslCertificate::CountryName: str = QLatin1String("C"); break; + case QSslCertificate::StateOrProvinceName: str = QLatin1String("ST"); break; + } + return str; +} + +/*! + \fn QString QSslCertificate::issuerInfo(SubjectInfo subject) const + + Returns the issuer information for the \a subject from the + certificate, or an empty string if there is no information for + \a subject in the certificate. + + \sa subjectInfo() +*/ +QString QSslCertificate::issuerInfo(SubjectInfo info) const +{ + // lazy init + if (d->issuerInfo.isEmpty() && d->x509) + d->issuerInfo = + _q_mapFromOnelineName(q_X509_NAME_oneline(q_X509_get_issuer_name(d->x509), 0, 0)); + + return d->issuerInfo.value(_q_SubjectInfoToString(info)); +} + +/*! + Returns the issuer information for \a tag from the certificate, + or an empty string if there is no information for \a tag in the + certificate. + + \sa subjectInfo() +*/ +QString QSslCertificate::issuerInfo(const QByteArray &tag) const +{ + // lazy init + if (d->issuerInfo.isEmpty() && d->x509) + d->issuerInfo = + _q_mapFromOnelineName(q_X509_NAME_oneline(q_X509_get_issuer_name(d->x509), 0, 0)); + + return d->issuerInfo.value(QString::fromLatin1(tag)); +} + +/*! + + \fn QString QSslCertificate::subjectInfo(SubjectInfo subject) const + + Returns the information for the \a subject, or an empty string if + there is no information for \a subject in the certificate. + + \sa issuerInfo() +*/ +QString QSslCertificate::subjectInfo(SubjectInfo info) const +{ + // lazy init + if (d->subjectInfo.isEmpty() && d->x509) + d->subjectInfo = + _q_mapFromOnelineName(q_X509_NAME_oneline(q_X509_get_subject_name(d->x509), 0, 0)); + + return d->subjectInfo.value(_q_SubjectInfoToString(info)); +} + +/*! + Returns the subject information for \a tag, or an empty string if + there is no information for \a tag in the certificate. + + \sa issuerInfo() +*/ +QString QSslCertificate::subjectInfo(const QByteArray &tag) const +{ + // lazy init + if (d->subjectInfo.isEmpty() && d->x509) + d->subjectInfo = + _q_mapFromOnelineName(q_X509_NAME_oneline(q_X509_get_subject_name(d->x509), 0, 0)); + + return d->subjectInfo.value(QString::fromLatin1(tag)); +} + +/*! + Returns the list of alternative subject names for this + certificate. The alternate subject names typically contain host + names, optionally with wildcards, that are valid for this + certificate. + + These names are tested against the connected peer's host name, if + either the subject information for \l CommonName doesn't define a + valid host name, or the subject info name doesn't match the peer's + host name. + + \sa subjectInfo() +*/ +QMultiMap<QSsl::AlternateNameEntryType, QString> QSslCertificate::alternateSubjectNames() const +{ + QMultiMap<QSsl::AlternateNameEntryType, QString> result; + + if (!d->x509) + return result; + + STACK_OF(GENERAL_NAME) *altNames = (STACK_OF(GENERAL_NAME)*)q_X509_get_ext_d2i(d->x509, NID_subject_alt_name, 0, 0); + + if (altNames) { + for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) { + const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i); + if (genName->type != GEN_DNS && genName->type != GEN_EMAIL) + continue; + + int len = q_ASN1_STRING_length(genName->d.ia5); + if (len < 0 || len >= 8192) { + // broken name + continue; + } + + const char *altNameStr = reinterpret_cast<const char *>(q_ASN1_STRING_data(genName->d.ia5)); + const QString altName = QString::fromLatin1(altNameStr, len); + if (genName->type == GEN_DNS) + result.insert(QSsl::DnsEntry, altName); + else if (genName->type == GEN_EMAIL) + result.insert(QSsl::EmailEntry, altName); + } + q_sk_pop_free((STACK*)altNames, reinterpret_cast<void(*)(void*)>(q_sk_free)); + } + + return result; +} + +/*! + Returns the date-time that the certificate becomes valid, or an + empty QDateTime if this is a null certificate. + + \sa expiryDate() +*/ +QDateTime QSslCertificate::effectiveDate() const +{ + return d->notValidBefore; +} + +/*! + Returns the date-time that the certificate expires, or an empty + QDateTime if this is a null certificate. + + \sa effectiveDate() +*/ +QDateTime QSslCertificate::expiryDate() const +{ + return d->notValidAfter; +} + +/*! + Returns a pointer to the native certificate handle, if there is + one, or a null pointer otherwise. + + You can use this handle, together with the native API, to access + extended information about the certificate. + + \warning Use of this function has a high probability of being + non-portable, and its return value may vary from platform to + platform or change from minor release to minor release. +*/ +Qt::HANDLE QSslCertificate::handle() const +{ + return Qt::HANDLE(d->x509); +} + +/*! + Returns the certificate subject's public key. +*/ +QSslKey QSslCertificate::publicKey() const +{ + if (!d->x509) + return QSslKey(); + + QSslKey key; + + key.d->type = QSsl::PublicKey; + X509_PUBKEY *xkey = d->x509->cert_info->key; + EVP_PKEY *pkey = q_X509_PUBKEY_get(xkey); + Q_ASSERT(pkey); + + if (q_EVP_PKEY_type(pkey->type) == EVP_PKEY_RSA) { + key.d->rsa = q_EVP_PKEY_get1_RSA(pkey); + key.d->algorithm = QSsl::Rsa; + key.d->isNull = false; + } else if (q_EVP_PKEY_type(pkey->type) == EVP_PKEY_DSA) { + key.d->dsa = q_EVP_PKEY_get1_DSA(pkey); + key.d->algorithm = QSsl::Dsa; + key.d->isNull = false; + } else if (q_EVP_PKEY_type(pkey->type) == EVP_PKEY_DH) { + // DH unsupported + } else { + // error? + } + + q_EVP_PKEY_free(pkey); + return key; +} + +/*! + Returns this certificate converted to a PEM (Base64) encoded + representation. +*/ +QByteArray QSslCertificate::toPem() const +{ + if (!d->x509) + return QByteArray(); + return d->QByteArray_from_X509(d->x509, QSsl::Pem); +} + +/*! + Returns this certificate converted to a DER (binary) encoded + representation. +*/ +QByteArray QSslCertificate::toDer() const +{ + if (!d->x509) + return QByteArray(); + return d->QByteArray_from_X509(d->x509, QSsl::Der); +} + +/*! + Searches all files in the \a path for certificates encoded in the + specified \a format and returns them in a list. \e must be a file or a + pattern matching one or more files, as specified by \a syntax. + + Example: + + \snippet doc/src/snippets/code/src_network_ssl_qsslcertificate.cpp 0 + + \sa fromData() +*/ +QList<QSslCertificate> QSslCertificate::fromPath(const QString &path, + QSsl::EncodingFormat format, + QRegExp::PatternSyntax syntax) +{ + // $, (,), *, +, ., ?, [, ,], ^, {, | and }. + int pos = -1; + if (syntax == QRegExp::Wildcard) + pos = path.indexOf(QRegExp(QLatin1String("[^\\][\\*\\?\\[\\]]"))); + else if (syntax != QRegExp::FixedString) + pos = path.indexOf(QRegExp(QLatin1String("[^\\][\\$\\(\\)\\*\\+\\.\\?\\[\\]\\^\\{\\}\\|]"))); + QString pathPrefix = path.left(pos); // == path if pos < 0 + if (pos != -1) + pathPrefix = pathPrefix.left(pathPrefix.lastIndexOf(QLatin1Char('/'))); + + // Special case - if the prefix ends up being nothing, use "." instead and + // chop off the first two characters from the glob'ed paths. + int startIndex = 0; + if (pathPrefix.trimmed().isEmpty()) { + if(path.startsWith(QLatin1Char('/'))) { + pathPrefix = path.left(path.indexOf(QRegExp(QLatin1String("[\\*\\?\\[]")))); + pathPrefix = path.left(path.lastIndexOf(QLatin1Char('/'))); + } else { + startIndex = 2; + pathPrefix = QLatin1String("."); + } + } + + // The path is a file. + if (pos == -1 && QFileInfo(pathPrefix).isFile()) { + QFile file(pathPrefix); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + return QSslCertificate::fromData(file.readAll(),format); + return QList<QSslCertificate>(); + } + + // The path can be a file or directory. + QList<QSslCertificate> certs; + QRegExp pattern(path, Qt::CaseSensitive, syntax); + QDirIterator it(pathPrefix, QDir::Files, QDirIterator::FollowSymlinks | QDirIterator::Subdirectories); + while (it.hasNext()) { + QString filePath = startIndex == 0 ? it.next() : it.next().mid(startIndex); + if (!pattern.exactMatch(filePath)) + continue; + + QFile file(filePath); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + certs += QSslCertificate::fromData(file.readAll(),format); + } + return certs; +} + +/*! + Searches for and parses all certificates in \a device that are + encoded in the specified \a format and returns them in a list of + certificates. + + \sa fromData() +*/ +QList<QSslCertificate> QSslCertificate::fromDevice(QIODevice *device, QSsl::EncodingFormat format) +{ + if (!device) { + qWarning("QSslCertificate::fromDevice: cannot read from a null device"); + return QList<QSslCertificate>(); + } + return fromData(device->readAll(), format); +} + +/*! + Searches for and parses all certificates in \a data that are + encoded in the specified \a format and returns them in a list of + certificates. + + \sa fromDevice() +*/ +QList<QSslCertificate> QSslCertificate::fromData(const QByteArray &data, QSsl::EncodingFormat format) +{ + return (format == QSsl::Pem) + ? QSslCertificatePrivate::certificatesFromPem(data) + : QSslCertificatePrivate::certificatesFromDer(data); +} + +void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format) +{ + if (!data.isEmpty()) { + QList<QSslCertificate> certs = (format == QSsl::Pem) + ? certificatesFromPem(data, 1) + : certificatesFromDer(data, 1); + if (!certs.isEmpty()) { + *this = *certs.first().d; + if (x509) + x509 = q_X509_dup(x509); + } + } +} + +#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" +#define ENDCERTSTRING "-----END CERTIFICATE-----" + +// ### refactor against QSsl::pemFromDer() etc. (to avoid redundant implementations) +QByteArray QSslCertificatePrivate::QByteArray_from_X509(X509 *x509, QSsl::EncodingFormat format) +{ + if (!x509) { + qWarning("QSslSocketBackendPrivate::X509_to_QByteArray: null X509"); + return QByteArray(); + } + + // Use i2d_X509 to convert the X509 to an array. + int length = q_i2d_X509(x509, 0); + QByteArray array; + array.resize(length); + char *data = array.data(); + char **dataP = &data; + unsigned char **dataPu = (unsigned char **)dataP; + if (q_i2d_X509(x509, dataPu) < 0) + return QByteArray(); + + if (format == QSsl::Der) + return array; + + // Convert to Base64 - wrap at 64 characters. + array = array.toBase64(); + QByteArray tmp; + for (int i = 0; i <= array.size() - 64; i += 64) { + tmp += QByteArray::fromRawData(array.data() + i, 64); + tmp += '\n'; + } + if (int remainder = array.size() % 64) { + tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); + tmp += '\n'; + } + + return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; +} + +static QMap<QString, QString> _q_mapFromOnelineName(char *name) +{ + QMap<QString, QString> info; + QString infoStr = QString::fromLocal8Bit(name); + q_CRYPTO_free(name); + + // ### The right-hand encoding seems to allow hex (Regulierungsbeh\xC8orde) + //entry.replace(QLatin1String("\\x"), QLatin1String("%")); + //entry = QUrl::fromPercentEncoding(entry.toLatin1()); + // ### See RFC-4630 for more details! + + QRegExp rx(QLatin1String("/([A-Za-z]+)=(.+)")); + + int pos = 0; + while ((pos = rx.indexIn(infoStr, pos)) != -1) { + const QString name = rx.cap(1); + + QString value = rx.cap(2); + const int valuePos = rx.pos(2); + + const int next = rx.indexIn(value); + if (next == -1) { + info.insert(name, value); + break; + } + + value = value.left(next); + info.insert(name, value); + pos = valuePos + value.length(); + } + + return info; +} + +QSslCertificate QSslCertificatePrivate::QSslCertificate_from_X509(X509 *x509) +{ + QSslCertificate certificate; + if (!x509 || !QSslSocket::supportsSsl()) + return certificate; + + ASN1_TIME *nbef = q_X509_get_notBefore(x509); + ASN1_TIME *naft = q_X509_get_notAfter(x509); + certificate.d->notValidBefore = q_getTimeFromASN1(nbef); + certificate.d->notValidAfter = q_getTimeFromASN1(naft); + certificate.d->null = false; + certificate.d->x509 = q_X509_dup(x509); + + return certificate; +} + +static bool matchLineFeed(const QByteArray &pem, int *offset) +{ + char ch = 0; + + // ignore extra whitespace at the end of the line + while (*offset < pem.size() && (ch = pem.at(*offset)) == ' ') + ++*offset; + + if (ch == '\n') { + *offset += 1; + return true; + } + if (ch == '\r' && pem.size() > (*offset + 1) && pem.at(*offset + 1) == '\n') { + *offset += 2; + return true; + } + return false; +} + +QList<QSslCertificate> QSslCertificatePrivate::certificatesFromPem(const QByteArray &pem, int count) +{ + QList<QSslCertificate> certificates; + QSslSocketPrivate::ensureInitialized(); + + int offset = 0; + while (count == -1 || certificates.size() < count) { + int startPos = pem.indexOf(BEGINCERTSTRING, offset); + if (startPos == -1) + break; + startPos += sizeof(BEGINCERTSTRING) - 1; + if (!matchLineFeed(pem, &startPos)) + break; + + int endPos = pem.indexOf(ENDCERTSTRING, startPos); + if (endPos == -1) + break; + + offset = endPos + sizeof(ENDCERTSTRING) - 1; + if (offset < pem.size() && !matchLineFeed(pem, &offset)) + break; + + QByteArray decoded = QByteArray::fromBase64( + QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); +#if OPENSSL_VERSION_NUMBER >= 0x00908000L + const unsigned char *data = (const unsigned char *)decoded.data(); +#else + unsigned char *data = (unsigned char *)decoded.data(); +#endif + + if (X509 *x509 = q_d2i_X509(0, &data, decoded.size())) { + certificates << QSslCertificate_from_X509(x509); + q_X509_free(x509); + } + } + + return certificates; +} + +QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count) +{ + QList<QSslCertificate> certificates; + QSslSocketPrivate::ensureInitialized(); + + +#if OPENSSL_VERSION_NUMBER >= 0x00908000L + const unsigned char *data = (const unsigned char *)der.data(); +#else + unsigned char *data = (unsigned char *)der.data(); +#endif + int size = der.size(); + + while (count == -1 || certificates.size() < count) { + if (X509 *x509 = q_d2i_X509(0, &data, size)) { + certificates << QSslCertificate_from_X509(x509); + q_X509_free(x509); + } else { + break; + } + size -= ((char *)data - der.data()); + } + + return certificates; +} + +// These certificates are known to be fraudulent and were created during the comodo +// compromise. See http://www.comodo.com/Comodo-Fraud-Incident-2011-03-23.html +static const char *certificate_blacklist[] = { + "04:7e:cb:e9:fc:a5:5f:7b:d0:9e:ae:36:e1:0c:ae:1e", + "f5:c8:6a:f3:61:62:f1:3a:64:f5:4f:6d:c9:58:7c:06", + "d7:55:8f:da:f5:f1:10:5b:b2:13:28:2b:70:77:29:a3", + "39:2a:43:4f:0e:07:df:1f:8a:a3:05:de:34:e0:c2:29", + "3e:75:ce:d4:6b:69:30:21:21:88:30:ae:86:a8:2a:71", + "e9:02:8b:95:78:e4:15:dc:1a:71:0a:2b:88:15:44:47", + "92:39:d5:34:8f:40:d1:69:5a:74:54:70:e1:f2:3f:43", + "b0:b7:13:3e:d0:96:f9:b5:6f:ae:91:c8:74:bd:3a:c0", + "d8:f3:5f:4e:b7:87:2b:2d:ab:06:92:e3:15:38:2f:b0", + 0 +}; + +bool QSslCertificatePrivate::isBlacklisted(const QSslCertificate &certificate) +{ + for (int a = 0; certificate_blacklist[a] != 0; a++) { + if (certificate.serialNumber() == certificate_blacklist[a]) + return true; + } + return false; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QSslCertificate &certificate) +{ + debug << "QSslCertificate(" + << certificate.version() + << ',' << certificate.serialNumber() + << ',' << certificate.digest().toBase64() + << ',' << certificate.issuerInfo(QSslCertificate::Organization) + << ',' << certificate.subjectInfo(QSslCertificate::Organization) + << ',' << certificate.alternateSubjectNames() +#ifndef QT_NO_TEXTSTREAM + << ',' << certificate.effectiveDate() + << ',' << certificate.expiryDate() +#endif + << ')'; + return debug; +} +QDebug operator<<(QDebug debug, QSslCertificate::SubjectInfo info) +{ + switch (info) { + case QSslCertificate::Organization: debug << "Organization"; break; + case QSslCertificate::CommonName: debug << "CommonName"; break; + case QSslCertificate::CountryName: debug << "CountryName"; break; + case QSslCertificate::LocalityName: debug << "LocalityName"; break; + case QSslCertificate::OrganizationalUnitName: debug << "OrganizationalUnitName"; break; + case QSslCertificate::StateOrProvinceName: debug << "StateOrProvinceName"; break; + } + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslcertificate.h b/src/network/ssl/qsslcertificate.h new file mode 100644 index 0000000000..e972ee7239 --- /dev/null +++ b/src/network/ssl/qsslcertificate.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** 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 QSSLCERTIFICATE_H +#define QSSLCERTIFICATE_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qcryptographichash.h> +#include <QtCore/qregexp.h> +#include <QtCore/qsharedpointer.h> +#include <QtNetwork/qssl.h> + +typedef struct x509_st X509; // ### check if this works + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +class QDateTime; +class QIODevice; +class QSslKey; +class QStringList; +template <typename T, typename U> class QMultiMap; + +class QSslCertificatePrivate; +class Q_NETWORK_EXPORT QSslCertificate +{ +public: + enum SubjectInfo { + Organization, + CommonName, + LocalityName, + OrganizationalUnitName, + CountryName, + StateOrProvinceName + }; + + QSslCertificate(QIODevice *device, QSsl::EncodingFormat format = QSsl::Pem); + QSslCertificate( // ### s/encoded/data (to be consistent with signature in .cpp file) ? + const QByteArray &encoded = QByteArray(), QSsl::EncodingFormat format = QSsl::Pem); + QSslCertificate(const QSslCertificate &other); + ~QSslCertificate(); + QSslCertificate &operator=(const QSslCertificate &other); + bool operator==(const QSslCertificate &other) const; + inline bool operator!=(const QSslCertificate &other) const { return !operator==(other); } + + bool isNull() const; + bool isValid() const; + void clear(); + + // Certificate info + QByteArray version() const; + QByteArray serialNumber() const; + QByteArray digest(QCryptographicHash::Algorithm algorithm = QCryptographicHash::Md5) const; + QString issuerInfo(SubjectInfo info) const; + QString issuerInfo(const QByteArray &tag) const; + QString subjectInfo(SubjectInfo info) const; + QString subjectInfo(const QByteArray &tag) const; + QMultiMap<QSsl::AlternateNameEntryType, QString> alternateSubjectNames() const; + QDateTime effectiveDate() const; + QDateTime expiryDate() const; + QSslKey publicKey() const; + + QByteArray toPem() const; + QByteArray toDer() const; + + static QList<QSslCertificate> fromPath( + const QString &path, QSsl::EncodingFormat format = QSsl::Pem, + QRegExp::PatternSyntax syntax = QRegExp::FixedString); + static QList<QSslCertificate> fromDevice( + QIODevice *device, QSsl::EncodingFormat format = QSsl::Pem); + static QList<QSslCertificate> fromData( + const QByteArray &data, QSsl::EncodingFormat format = QSsl::Pem); + + Qt::HANDLE handle() const; + +private: + QExplicitlySharedDataPointer<QSslCertificatePrivate> d; + friend class QSslCertificatePrivate; + friend class QSslSocketBackendPrivate; +}; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslCertificate &certificate); +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QSslCertificate::SubjectInfo info); +#endif + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/ssl/qsslcertificate_p.h b/src/network/ssl/qsslcertificate_p.h new file mode 100644 index 0000000000..1ce33d3bfd --- /dev/null +++ b/src/network/ssl/qsslcertificate_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 QSSLCERTIFICATE_P_H +#define QSSLCERTIFICATE_P_H + +#include "qsslcertificate.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qsslsocket_p.h" +#include <QtCore/qdatetime.h> +#include <QtCore/qmap.h> + +#include <openssl/x509.h> + +QT_BEGIN_NAMESPACE + +class QSslCertificatePrivate +{ +public: + QSslCertificatePrivate() + : null(true), x509(0) + { + QSslSocketPrivate::ensureInitialized(); + } + + ~QSslCertificatePrivate() + { + if (x509) + q_X509_free(x509); + } + + bool null; + QByteArray versionString; + QByteArray serialNumberString; + + QMap<QString, QString> issuerInfo; + QMap<QString, QString> subjectInfo; + QDateTime notValidAfter; + QDateTime notValidBefore; + + X509 *x509; + + void init(const QByteArray &data, QSsl::EncodingFormat format); + + static QByteArray QByteArray_from_X509(X509 *x509, QSsl::EncodingFormat format); + static QSslCertificate QSslCertificate_from_X509(X509 *x509); + static QList<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count = -1); + static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count = -1); + static bool isBlacklisted(const QSslCertificate &certificate); + + friend class QSslSocketBackendPrivate; + + QAtomicInt ref; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslcipher.cpp b/src/network/ssl/qsslcipher.cpp new file mode 100644 index 0000000000..33d4b66a50 --- /dev/null +++ b/src/network/ssl/qsslcipher.cpp @@ -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$ +** +****************************************************************************/ + + +/*! + \class QSslCipher + \brief The QSslCipher class represents an SSL cryptographic cipher. + \since 4.3 + + \reentrant + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + QSslCipher stores information about one cryptographic cipher. It + is most commonly used with QSslSocket, either for configuring + which ciphers the socket can use, or for displaying the socket's + ciphers to the user. + + \sa QSslSocket, QSslKey +*/ + +#include "qsslcipher.h" +#include "qsslcipher_p.h" +#include "qsslsocket.h" + +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + Constructs an empty QSslCipher object. +*/ +QSslCipher::QSslCipher() + : d(new QSslCipherPrivate) +{ +} + +/*! + Constructs a QSslCipher object for the cipher determined by \a + name and \a protocol. The constructor accepts only supported + ciphers (i.e., the \a name and \a protocol must identify a cipher + in the list of ciphers returned by + QSslSocket::supportedCiphers()). + + You can call isNull() after construction to check if \a name and + \a protocol correctly identified a supported cipher. +*/ +QSslCipher::QSslCipher(const QString &name, QSsl::SslProtocol protocol) + : d(new QSslCipherPrivate) +{ + foreach (const QSslCipher &cipher, QSslSocket::supportedCiphers()) { + if (cipher.name() == name && cipher.protocol() == protocol) { + *this = cipher; + return; + } + } +} + +/*! + Constructs an identical copy of the \a other cipher. +*/ +QSslCipher::QSslCipher(const QSslCipher &other) + : d(new QSslCipherPrivate) +{ + *d.data() = *other.d.data(); +} + +/*! + Destroys the QSslCipher object. +*/ +QSslCipher::~QSslCipher() +{ +} + +/*! + Copies the contents of \a other into this cipher, making the two + ciphers identical. +*/ +QSslCipher &QSslCipher::operator=(const QSslCipher &other) +{ + *d.data() = *other.d.data(); + return *this; +} + +/*! + Returns true if this cipher is the same as \a other; otherwise, + false is returned. +*/ +bool QSslCipher::operator==(const QSslCipher &other) const +{ + return d->name == other.d->name && d->protocol == other.d->protocol; +} + +/*! + \fn bool QSslCipher::operator!=(const QSslCipher &other) const + + Returns true if this cipher is not the same as \a other; + otherwise, false is returned. +*/ + +/*! + Returns true if this is a null cipher; otherwise returns false. +*/ +bool QSslCipher::isNull() const +{ + return d->isNull; +} + +/*! + Returns the name of the cipher, or an empty QString if this is a null + cipher. + + \sa isNull() +*/ +QString QSslCipher::name() const +{ + return d->name; +} + +/*! + Returns the number of bits supported by the cipher. + + \sa usedBits() +*/ +int QSslCipher::supportedBits()const +{ + return d->supportedBits; +} + +/*! + Returns the number of bits used by the cipher. + + \sa supportedBits() +*/ +int QSslCipher::usedBits() const +{ + return d->bits; +} + +/*! + Returns the cipher's key exchange method as a QString. +*/ +QString QSslCipher::keyExchangeMethod() const +{ + return d->keyExchangeMethod; +} + +/*! + Returns the cipher's authentication method as a QString. +*/ +QString QSslCipher::authenticationMethod() const +{ + return d->authenticationMethod; +} + +/*! + Returns the cipher's encryption method as a QString. +*/ +QString QSslCipher::encryptionMethod() const +{ + return d->encryptionMethod; +} + +/*! + Returns the cipher's protocol as a QString. + + \sa protocol() +*/ +QString QSslCipher::protocolString() const +{ + return d->protocolString; +} + +/*! + Returns the cipher's protocol type, or \l QSsl::UnknownProtocol if + QSslCipher is unable to determine the protocol (protocolString() may + contain more information). + + \sa protocolString() +*/ +QSsl::SslProtocol QSslCipher::protocol() const +{ + return d->protocol; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QSslCipher &cipher) +{ + debug << "QSslCipher(name=" << qPrintable(cipher.name()) + << ", bits=" << cipher.usedBits() + << ", proto=" << qPrintable(cipher.protocolString()) + << ')'; + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslcipher.h b/src/network/ssl/qsslcipher.h new file mode 100644 index 0000000000..edaed2c2e8 --- /dev/null +++ b/src/network/ssl/qsslcipher.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 QSSLCIPHER_H +#define QSSLCIPHER_H + +#include <QtCore/qstring.h> +#include <QtCore/qscopedpointer.h> +#include <QtNetwork/qssl.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +class QSslCipherPrivate; +class Q_NETWORK_EXPORT QSslCipher +{ +public: + QSslCipher(); + QSslCipher(const QString &name, QSsl::SslProtocol protocol); + QSslCipher(const QSslCipher &other); + ~QSslCipher(); + QSslCipher &operator=(const QSslCipher &other); + bool operator==(const QSslCipher &other) const; + inline bool operator!=(const QSslCipher &other) const { return !operator==(other); } + + bool isNull() const; + QString name() const; + int supportedBits() const; + int usedBits() const; + + QString keyExchangeMethod() const; + QString authenticationMethod() const; + QString encryptionMethod() const; + QString protocolString() const; + QSsl::SslProtocol protocol() const; + +private: + QScopedPointer<QSslCipherPrivate> d; + friend class QSslSocketBackendPrivate; +}; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslCipher &cipher); +#endif + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/network/ssl/qsslcipher_p.h b/src/network/ssl/qsslcipher_p.h new file mode 100644 index 0000000000..79fe911280 --- /dev/null +++ b/src/network/ssl/qsslcipher_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 "qsslcipher.h" + +QT_BEGIN_NAMESPACE + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +class QSslCipherPrivate +{ +public: + QSslCipherPrivate() + : isNull(true), supportedBits(0), bits(0), + exportable(false), protocol(QSsl::UnknownProtocol) + { + } + + bool isNull; + QString name; + int supportedBits; + int bits; + QString keyExchangeMethod; + QString authenticationMethod; + QString encryptionMethod; + bool exportable; + QString protocolString; + QSsl::SslProtocol protocol; +}; + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp new file mode 100644 index 0000000000..70d7dd8df1 --- /dev/null +++ b/src/network/ssl/qsslconfiguration.cpp @@ -0,0 +1,542 @@ +/**************************************************************************** +** +** 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 "qsslconfiguration.h" +#include "qsslconfiguration_p.h" +#include "qsslsocket.h" +#include "qmutex.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QSslConfiguration + \brief The QSslConfiguration class holds the configuration and state of an SSL connection + \since 4.4 + + \reentrant + \inmodule QtNetwork + \ingroup network + \ingroup ssl + + QSslConfiguration is used by Qt networking classes to relay + information about an open SSL connection and to allow the + application to control certain features of that connection. + + The settings that QSslConfiguration currently supports are: + + \list + \o The SSL/TLS protocol to be used + \o The certificate to be presented to the peer during connection + and its associated private key + \o The ciphers allowed to be used for encrypting the connection + \o The list of Certificate Authorities certificates that are + used to validate the peer's certificate + \endlist + + These settings are applied only during the connection + handshake. Setting them after the connection has been established + has no effect. + + The state that QSslConfiguration supports are: + \list + \o The certificate the peer presented during handshake, along + with the chain leading to a CA certificate + \o The cipher used to encrypt this session + \endlist + + The state can only be obtained once the SSL connection starts, but + not necessarily before it's done. Some settings may change during + the course of the SSL connection without need to restart it (for + instance, the cipher can be changed over time). + + State in QSslConfiguration objects cannot be changed. + + QSslConfiguration can be used with QSslSocket and the Network + Access API. + + Note that changing settings in QSslConfiguration is not enough to + change the settings in the related SSL connection. You must call + setSslConfiguration on a modified QSslConfiguration object to + achieve that. The following example illustrates how to change the + protocol to TLSv1 in a QSslSocket object: + + \snippet doc/src/snippets/code/src_network_ssl_qsslconfiguration.cpp 0 + + \sa QSsl::SslProtocol, QSslCertificate, QSslCipher, QSslKey + QSslSocket, QNetworkAccessManager, + QSslSocket::sslConfiguration(), QSslSocket::setSslConfiguration() +*/ + +/*! + Constructs an empty SSL configuration. This configuration contains + no valid settings and the state will be empty. isNull() will + return true after this constructor is called. + + Once any setter methods are called, isNull() will return false. +*/ +QSslConfiguration::QSslConfiguration() + : d(new QSslConfigurationPrivate) +{ +} + +/*! + Copies the configuration and state of \a other. If \a other is + null, this object will be null too. +*/ +QSslConfiguration::QSslConfiguration(const QSslConfiguration &other) + : d(other.d) +{ +} + +/*! + Releases any resources held by QSslConfiguration. +*/ +QSslConfiguration::~QSslConfiguration() +{ + // QSharedDataPointer deletes d for us if necessary +} + +/*! + Copies the configuration and state of \a other. If \a other is + null, this object will be null too. +*/ +QSslConfiguration &QSslConfiguration::operator=(const QSslConfiguration &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this QSslConfiguration object is equal to \a + other. + + Two QSslConfiguration objects are considered equal if they have + the exact same settings and state. + + \sa operator!=() +*/ +bool QSslConfiguration::operator==(const QSslConfiguration &other) const +{ + if (d == other.d) + return true; + return d->peerCertificate == other.d->peerCertificate && + d->peerCertificateChain == other.d->peerCertificateChain && + d->localCertificate == other.d->localCertificate && + d->privateKey == other.d->privateKey && + d->sessionCipher == other.d->sessionCipher && + d->ciphers == other.d->ciphers && + d->caCertificates == other.d->caCertificates && + d->protocol == other.d->protocol && + d->peerVerifyMode == other.d->peerVerifyMode && + d->peerVerifyDepth == other.d->peerVerifyDepth; +} + +/*! + \fn QSslConfiguration::operator!=(const QSslConfiguration &other) const + + Returns true if this QSslConfiguration differs from \a other. Two + QSslConfiguration objects are considered different if any state or + setting is different. + + \sa operator==() +*/ + +/*! + Returns true if this is a null QSslConfiguration object. + + A QSslConfiguration object is null if it has been + default-constructed and no setter methods have been called. + + \sa setProtocol(), setLocalCertificate(), setPrivateKey(), + setCiphers(), setCaCertificates() +*/ +bool QSslConfiguration::isNull() const +{ + return (d->protocol == QSsl::SecureProtocols && + d->peerVerifyMode == QSslSocket::AutoVerifyPeer && + d->peerVerifyDepth == 0 && + d->caCertificates.count() == 0 && + d->ciphers.count() == 0 && + d->localCertificate.isNull() && + d->privateKey.isNull() && + d->peerCertificate.isNull() && + d->peerCertificateChain.count() == 0); +} + +/*! + Returns the protocol setting for this SSL configuration. + + \sa setProtocol() +*/ +QSsl::SslProtocol QSslConfiguration::protocol() const +{ + return d->protocol; +} + +/*! + Sets the protocol setting for this configuration to be \a + protocol. + + Setting the protocol once the connection has already been + established has no effect. + + \sa protocol() +*/ +void QSslConfiguration::setProtocol(QSsl::SslProtocol protocol) +{ + d->protocol = protocol; +} + +/*! + Returns the verify mode. This mode decides whether QSslSocket should + request a certificate from the peer (i.e., the client requests a + certificate from the server, or a server requesting a certificate from the + client), and whether it should require that this certificate is valid. + + The default mode is AutoVerifyPeer, which tells QSslSocket to use + VerifyPeer for clients, QueryPeer for clients. + + \sa setPeerVerifyMode() +*/ +QSslSocket::PeerVerifyMode QSslConfiguration::peerVerifyMode() const +{ + return d->peerVerifyMode; +} + +/*! + Sets the verify mode to \a mode. This mode decides whether QSslSocket + should request a certificate from the peer (i.e., the client requests a + certificate from the server, or a server requesting a certificate from the + client), and whether it should require that this certificate is valid. + + The default mode is AutoVerifyPeer, which tells QSslSocket to use + VerifyPeer for clients, QueryPeer for clients. + + \sa peerVerifyMode() +*/ +void QSslConfiguration::setPeerVerifyMode(QSslSocket::PeerVerifyMode mode) +{ + d->peerVerifyMode = mode; +} + + +/*! + Returns the maximum number of certificates in the peer's certificate chain + to be checked during the SSL handshake phase, or 0 (the default) if no + maximum depth has been set, indicating that the whole certificate chain + should be checked. + + The certificates are checked in issuing order, starting with the peer's + own certificate, then its issuer's certificate, and so on. + + \sa setPeerVerifyDepth(), peerVerifyMode() +*/ +int QSslConfiguration::peerVerifyDepth() const +{ + return d->peerVerifyDepth; +} + +/*! + Sets the maximum number of certificates in the peer's certificate chain to + be checked during the SSL handshake phase, to \a depth. Setting a depth of + 0 means that no maximum depth is set, indicating that the whole + certificate chain should be checked. + + The certificates are checked in issuing order, starting with the peer's + own certificate, then its issuer's certificate, and so on. + + \sa peerVerifyDepth(), setPeerVerifyMode() +*/ +void QSslConfiguration::setPeerVerifyDepth(int depth) +{ + if (depth < 0) { + qWarning("QSslConfiguration::setPeerVerifyDepth: cannot set negative depth of %d", depth); + return; + } + d->peerVerifyDepth = depth; +} + +/*! + Returns the certificate to be presented to the peer during the SSL + handshake process. + + \sa setLocalCertificate() +*/ +QSslCertificate QSslConfiguration::localCertificate() const +{ + return d->localCertificate; +} + +/*! + Sets the certificate to be presented to the peer during SSL + handshake to be \a certificate. + + Setting the certificate once the connection has been established + has no effect. + + A certificate is the means of identification used in the SSL + process. The local certificate is used by the remote end to verify + the local user's identity against its list of Certification + Authorities. In most cases, such as in HTTP web browsing, only + servers identify to the clients, so the client does not send a + certificate. + + \sa localCertificate() +*/ +void QSslConfiguration::setLocalCertificate(const QSslCertificate &certificate) +{ + d->localCertificate = certificate; +} + +/*! + Returns the peer's digital certificate (i.e., the immediate + certificate of the host you are connected to), or a null + certificate, if the peer has not assigned a certificate. + + The peer certificate is checked automatically during the + handshake phase, so this function is normally used to fetch + the certificate for display or for connection diagnostic + purposes. It contains information about the peer, including + its host name, the certificate issuer, and the peer's public + key. + + Because the peer certificate is set during the handshake phase, it + is safe to access the peer certificate from a slot connected to + the QSslSocket::sslErrors() signal, QNetworkReply::sslErrors() + signal, or the QSslSocket::encrypted() signal. + + If a null certificate is returned, it can mean the SSL handshake + failed, or it can mean the host you are connected to doesn't have + a certificate, or it can mean there is no connection. + + If you want to check the peer's complete chain of certificates, + use peerCertificateChain() to get them all at once. + + \sa peerCertificateChain(), + QSslSocket::sslErrors(), QSslSocket::ignoreSslErrors(), + QNetworkReply::sslErrors(), QNetworkReply::ignoreSslErrors() +*/ +QSslCertificate QSslConfiguration::peerCertificate() const +{ + return d->peerCertificate; +} + +/*! + Returns the peer's chain of digital certificates, starting with + the peer's immediate certificate and ending with the CA's + certificate. + + Peer certificates are checked automatically during the handshake + phase. This function is normally used to fetch certificates for + display, or for performing connection diagnostics. Certificates + contain information about the peer and the certificate issuers, + including host name, issuer names, and issuer public keys. + + Because the peer certificate is set during the handshake phase, it + is safe to access the peer certificate from a slot connected to + the QSslSocket::sslErrors() signal, QNetworkReply::sslErrors() + signal, or the QSslSocket::encrypted() signal. + + If an empty list is returned, it can mean the SSL handshake + failed, or it can mean the host you are connected to doesn't have + a certificate, or it can mean there is no connection. + + If you want to get only the peer's immediate certificate, use + peerCertificate(). + + \sa peerCertificate(), + QSslSocket::sslErrors(), QSslSocket::ignoreSslErrors(), + QNetworkReply::sslErrors(), QNetworkReply::ignoreSslErrors() +*/ +QList<QSslCertificate> QSslConfiguration::peerCertificateChain() const +{ + return d->peerCertificateChain; +} + +/*! + Returns the socket's cryptographic \l {QSslCipher} {cipher}, or a + null cipher if the connection isn't encrypted. The socket's cipher + for the session is set during the handshake phase. The cipher is + used to encrypt and decrypt data transmitted through the socket. + + The SSL infrastructure also provides functions for setting the + ordered list of ciphers from which the handshake phase will + eventually select the session cipher. This ordered list must be in + place before the handshake phase begins. + + \sa ciphers(), setCiphers(), QSslSocket::supportedCiphers() +*/ +QSslCipher QSslConfiguration::sessionCipher() const +{ + return d->sessionCipher; +} + +/*! + Returns the \l {QSslKey} {SSL key} assigned to this connection or + a null key if none has been assigned yet. + + \sa setPrivateKey(), localCertificate() +*/ +QSslKey QSslConfiguration::privateKey() const +{ + return d->privateKey; +} + +/*! + Sets the connection's private \l {QSslKey} {key} to \a key. The + private key and the local \l {QSslCertificate} {certificate} are + used by clients and servers that must prove their identity to + SSL peers. + + Both the key and the local certificate are required if you are + creating an SSL server socket. If you are creating an SSL client + socket, the key and local certificate are required if your client + must identify itself to an SSL server. + + \sa privateKey(), setLocalCertificate() +*/ +void QSslConfiguration::setPrivateKey(const QSslKey &key) +{ + d->privateKey = key; +} + +/*! + Returns this connection's current cryptographic cipher suite. This + list is used during the handshake phase for choosing a + session cipher. The returned list of ciphers is ordered by + descending preference. (i.e., the first cipher in the list is the + most preferred cipher). The session cipher will be the first one + in the list that is also supported by the peer. + + By default, the handshake phase can choose any of the ciphers + supported by this system's SSL libraries, which may vary from + system to system. The list of ciphers supported by this system's + SSL libraries is returned by QSslSocket::supportedCiphers(). You can restrict + the list of ciphers used for choosing the session cipher for this + socket by calling setCiphers() with a subset of the supported + ciphers. You can revert to using the entire set by calling + setCiphers() with the list returned by QSslSocket::supportedCiphers(). + + \sa setCiphers(), QSslSocket::supportedCiphers() +*/ +QList<QSslCipher> QSslConfiguration::ciphers() const +{ + return d->ciphers; +} + +/*! + Sets the cryptographic cipher suite for this socket to \a ciphers, + which must contain a subset of the ciphers in the list returned by + supportedCiphers(). + + Restricting the cipher suite must be done before the handshake + phase, where the session cipher is chosen. + + \sa ciphers(), QSslSocket::supportedCiphers() +*/ +void QSslConfiguration::setCiphers(const QList<QSslCipher> &ciphers) +{ + d->ciphers = ciphers; +} + +/*! + Returns this connection's CA certificate database. The CA certificate + database is used by the socket during the handshake phase to + validate the peer's certificate. It can be modified prior to the + handshake with setCaCertificates(), or with \l{QSslSocket}'s + \l{QSslSocket::}{addCaCertificate()} and + \l{QSslSocket::}{addCaCertificates()}. + + \sa setCaCertificates() +*/ +QList<QSslCertificate> QSslConfiguration::caCertificates() const +{ + return d->caCertificates; +} + +/*! + Sets this socket's CA certificate database to be \a certificates. + The certificate database must be set prior to the SSL handshake. + The CA certificate database is used by the socket during the + handshake phase to validate the peer's certificate. + + \sa caCertificates() +*/ +void QSslConfiguration::setCaCertificates(const QList<QSslCertificate> &certificates) +{ + d->caCertificates = certificates; +} + +/*! + Returns the default SSL configuration to be used in new SSL + connections. + + The default SSL configuration consists of: + + \list + \o no local certificate and no private key + \o protocol SecureProtocols (meaning either TLS 1.0 or SSL 3 will be used) + \o the system's default CA certificate list + \o the cipher list equal to the list of the SSL libraries' + supported SSL ciphers + \endlist + + \sa QSslSocket::supportedCiphers(), setDefaultConfiguration() +*/ +QSslConfiguration QSslConfiguration::defaultConfiguration() +{ + return QSslConfigurationPrivate::defaultConfiguration(); +} + +/*! + Sets the default SSL configuration to be used in new SSL + connections to be \a configuration. Existing connections are not + affected by this call. + + \sa QSslSocket::supportedCiphers(), defaultConfiguration() +*/ +void QSslConfiguration::setDefaultConfiguration(const QSslConfiguration &configuration) +{ + QSslConfigurationPrivate::setDefaultConfiguration(configuration); +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h new file mode 100644 index 0000000000..143566bef1 --- /dev/null +++ b/src/network/ssl/qsslconfiguration.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, Nokia gives permission to link +** the code of its release of Qt with the OpenSSL project's "OpenSSL" library +** (or modified versions of the "OpenSSL" library that use the same license +** as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLCONFIGURATION_H +#define QSSLCONFIGURATION_H + +#include <QtCore/qshareddata.h> +#include <QtNetwork/qsslsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +template<typename T> class QList; +class QSslCertificate; +class QSslCipher; +class QSslKey; + +class QSslConfigurationPrivate; +class Q_NETWORK_EXPORT QSslConfiguration +{ +public: + QSslConfiguration(); + QSslConfiguration(const QSslConfiguration &other); + ~QSslConfiguration(); + QSslConfiguration &operator=(const QSslConfiguration &other); + + bool operator==(const QSslConfiguration &other) const; + inline bool operator!=(const QSslConfiguration &other) const + { return !(*this == other); } + + bool isNull() const; // ### Qt 5: remove; who would need this? + + QSsl::SslProtocol protocol() const; + void setProtocol(QSsl::SslProtocol protocol); + + // Verification + QSslSocket::PeerVerifyMode peerVerifyMode() const; + void setPeerVerifyMode(QSslSocket::PeerVerifyMode mode); + + int peerVerifyDepth() const; + void setPeerVerifyDepth(int depth); + + // Certificate & cipher configuration + QSslCertificate localCertificate() const; + void setLocalCertificate(const QSslCertificate &certificate); + + QSslCertificate peerCertificate() const; + QList<QSslCertificate> peerCertificateChain() const; + QSslCipher sessionCipher() const; + + // Private keys, for server sockets + QSslKey privateKey() const; + void setPrivateKey(const QSslKey &key); + + // Cipher settings + QList<QSslCipher> ciphers() const; + void setCiphers(const QList<QSslCipher> &ciphers); + + // Certificate Authority (CA) settings + QList<QSslCertificate> caCertificates() const; + void setCaCertificates(const QList<QSslCertificate> &certificates); + + static QSslConfiguration defaultConfiguration(); + static void setDefaultConfiguration(const QSslConfiguration &configuration); + +private: + friend class QSslSocket; + friend class QSslConfigurationPrivate; + QSslConfiguration(QSslConfigurationPrivate *dd); + QSharedDataPointer<QSslConfigurationPrivate> d; +}; + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h new file mode 100644 index 0000000000..a5af51a8db --- /dev/null +++ b/src/network/ssl/qsslconfiguration_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, Nokia gives permission to link +** the code of its release of Qt with the OpenSSL project's "OpenSSL" library +** (or modified versions of the "OpenSSL" library that use the same license +** as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLCONFIGURATION_P_H +#define QSSLCONFIGURATION_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 QSslSocket API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qsslconfiguration.h" +#include "qlist.h" +#include "qsslcertificate.h" +#include "qsslcipher.h" +#include "qsslkey.h" + +QT_BEGIN_NAMESPACE + +class QSslConfigurationPrivate: public QSharedData +{ +public: + QSslConfigurationPrivate() + : protocol(QSsl::SecureProtocols), + peerVerifyMode(QSslSocket::AutoVerifyPeer), + peerVerifyDepth(0) + { } + + QSslCertificate peerCertificate; + QList<QSslCertificate> peerCertificateChain; + QSslCertificate localCertificate; + + QSslKey privateKey; + QSslCipher sessionCipher; + QList<QSslCipher> ciphers; + QList<QSslCertificate> caCertificates; + + QSsl::SslProtocol protocol; + QSslSocket::PeerVerifyMode peerVerifyMode; + int peerVerifyDepth; + + // in qsslsocket.cpp: + static QSslConfiguration defaultConfiguration(); + static void setDefaultConfiguration(const QSslConfiguration &configuration); + static void deepCopyDefaultConfiguration(QSslConfigurationPrivate *config); +}; + +// implemented here for inlining purposes +inline QSslConfiguration::QSslConfiguration(QSslConfigurationPrivate *dd) + : d(dd) +{ +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslerror.cpp b/src/network/ssl/qsslerror.cpp new file mode 100644 index 0000000000..ae18b47170 --- /dev/null +++ b/src/network/ssl/qsslerror.cpp @@ -0,0 +1,321 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +/*! + \class QSslError + \brief The QSslError class provides an SSL error. + \since 4.3 + + \reentrant + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + QSslError provides a simple API for managing errors during QSslSocket's + SSL handshake. + + \sa QSslSocket, QSslCertificate, QSslCipher +*/ + +/*! + \enum QSslError::SslError + + Describes all recognized errors that can occur during an SSL handshake. + + \value NoError + \value UnableToGetIssuerCertificate + \value UnableToDecryptCertificateSignature + \value UnableToDecodeIssuerPublicKey + \value CertificateSignatureFailed + \value CertificateNotYetValid + \value CertificateExpired + \value InvalidNotBeforeField + \value InvalidNotAfterField + \value SelfSignedCertificate + \value SelfSignedCertificateInChain + \value UnableToGetLocalIssuerCertificate + \value UnableToVerifyFirstCertificate + \value CertificateRevoked + \value InvalidCaCertificate + \value PathLengthExceeded + \value InvalidPurpose + \value CertificateUntrusted + \value CertificateRejected + \value SubjectIssuerMismatch + \value AuthorityIssuerSerialNumberMismatch + \value NoPeerCertificate + \value HostNameMismatch + \value UnspecifiedError + \value NoSslSupport + \value CertificateBlacklisted + + \sa QSslError::errorString() +*/ + +#include "qsslerror.h" +#include "qsslsocket.h" +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE +#endif + +class QSslErrorPrivate +{ +public: + QSslError::SslError error; + QSslCertificate certificate; +}; + +/*! + Constructs a QSslError object with no error and default certificate. + +*/ + +// RVCT compiler in debug build does not like about default values in const- +// So as an workaround we define all constructor overloads here explicitly +QSslError::QSslError() + : d(new QSslErrorPrivate) +{ + d->error = QSslError::NoError; + d->certificate = QSslCertificate(); +} + +/*! + Constructs a QSslError object. The argument specifies the \a + error that occurred. + +*/ +QSslError::QSslError(SslError error) + : d(new QSslErrorPrivate) +{ + d->error = error; + d->certificate = QSslCertificate(); +} + +/*! + Constructs a QSslError object. The two arguments specify the \a + error that occurred, and which \a certificate the error relates to. + + \sa QSslCertificate +*/ +QSslError::QSslError(SslError error, const QSslCertificate &certificate) + : d(new QSslErrorPrivate) +{ + d->error = error; + d->certificate = certificate; +} + +/*! + Constructs an identical copy of \a other. +*/ +QSslError::QSslError(const QSslError &other) + : d(new QSslErrorPrivate) +{ + *d.data() = *other.d.data(); +} + +/*! + Destroys the QSslError object. +*/ +QSslError::~QSslError() +{ +} + +/*! + \since 4.4 + + Assigns the contents of \a other to this error. +*/ +QSslError &QSslError::operator=(const QSslError &other) +{ + *d.data() = *other.d.data(); + return *this; +} + +/*! + \since 4.4 + + Returns true if this error is equal to \a other; otherwise returns false. +*/ +bool QSslError::operator==(const QSslError &other) const +{ + return d->error == other.d->error + && d->certificate == other.d->certificate; +} + +/*! + \fn bool QSslError::operator!=(const QSslError &other) const + \since 4.4 + + Returns true if this error is not equal to \a other; otherwise returns + false. +*/ + +/*! + Returns the type of the error. + + \sa errorString(), certificate() +*/ +QSslError::SslError QSslError::error() const +{ + return d->error; +} + +/*! + Returns a short localized human-readable description of the error. + + \sa error(), certificate() +*/ +QString QSslError::errorString() const +{ + QString errStr; + switch (d->error) { + case NoError: + errStr = QSslSocket::tr("No error"); + break; + case UnableToGetIssuerCertificate: + errStr = QSslSocket::tr("The issuer certificate could not be found"); + break; + case UnableToDecryptCertificateSignature: + errStr = QSslSocket::tr("The certificate signature could not be decrypted"); + break; + case UnableToDecodeIssuerPublicKey: + errStr = QSslSocket::tr("The public key in the certificate could not be read"); + break; + case CertificateSignatureFailed: + errStr = QSslSocket::tr("The signature of the certificate is invalid"); + break; + case CertificateNotYetValid: + errStr = QSslSocket::tr("The certificate is not yet valid"); + break; + case CertificateExpired: + errStr = QSslSocket::tr("The certificate has expired"); + break; + case InvalidNotBeforeField: + errStr = QSslSocket::tr("The certificate's notBefore field contains an invalid time"); + break; + case InvalidNotAfterField: + errStr = QSslSocket::tr("The certificate's notAfter field contains an invalid time"); + break; + case SelfSignedCertificate: + errStr = QSslSocket::tr("The certificate is self-signed, and untrusted"); + break; + case SelfSignedCertificateInChain: + errStr = QSslSocket::tr("The root certificate of the certificate chain is self-signed, and untrusted"); + break; + case UnableToGetLocalIssuerCertificate: + errStr = QSslSocket::tr("The issuer certificate of a locally looked up certificate could not be found"); + break; + case UnableToVerifyFirstCertificate: + errStr = QSslSocket::tr("No certificates could be verified"); + break; + case InvalidCaCertificate: + errStr = QSslSocket::tr("One of the CA certificates is invalid"); + break; + case PathLengthExceeded: + errStr = QSslSocket::tr("The basicConstraints path length parameter has been exceeded"); + break; + case InvalidPurpose: + errStr = QSslSocket::tr("The supplied certificate is unsuitable for this purpose"); + break; + case CertificateUntrusted: + errStr = QSslSocket::tr("The root CA certificate is not trusted for this purpose"); + break; + case CertificateRejected: + errStr = QSslSocket::tr("The root CA certificate is marked to reject the specified purpose"); + break; + case SubjectIssuerMismatch: // hostname mismatch + errStr = QSslSocket::tr("The current candidate issuer certificate was rejected because its" + " subject name did not match the issuer name of the current certificate"); + break; + case AuthorityIssuerSerialNumberMismatch: + errStr = QSslSocket::tr("The current candidate issuer certificate was rejected because" + " its issuer name and serial number was present and did not match the" + " authority key identifier of the current certificate"); + break; + case NoPeerCertificate: + errStr = QSslSocket::tr("The peer did not present any certificate"); + break; + case HostNameMismatch: + errStr = QSslSocket::tr("The host name did not match any of the valid hosts" + " for this certificate"); + break; + case NoSslSupport: + break; + case CertificateBlacklisted: + errStr = QSslSocket::tr("The peer certificate is blacklisted"); + break; + default: + errStr = QSslSocket::tr("Unknown error"); + break; + } + + return errStr; +} + +/*! + Returns the certificate associated with this error, or a null certificate + if the error does not relate to any certificate. + + \sa error(), errorString() +*/ +QSslCertificate QSslError::certificate() const +{ + return d->certificate; +} + +#ifndef QT_NO_DEBUG_STREAM +//class QDebug; +QDebug operator<<(QDebug debug, const QSslError &error) +{ + debug << error.errorString(); + return debug; +} +QDebug operator<<(QDebug debug, const QSslError::SslError &error) +{ + debug << QSslError(error).errorString(); + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslerror.h b/src/network/ssl/qsslerror.h new file mode 100644 index 0000000000..c30c02a8af --- /dev/null +++ b/src/network/ssl/qsslerror.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 QSSLERROR_H +#define QSSLERROR_H + +#include <QtCore/qvariant.h> +#include <QtNetwork/qsslcertificate.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +class QSslErrorPrivate; +class Q_NETWORK_EXPORT QSslError +{ +public: + enum SslError { + NoError, + UnableToGetIssuerCertificate, + UnableToDecryptCertificateSignature, + UnableToDecodeIssuerPublicKey, + CertificateSignatureFailed, + CertificateNotYetValid, + CertificateExpired, + InvalidNotBeforeField, + InvalidNotAfterField, + SelfSignedCertificate, + SelfSignedCertificateInChain, + UnableToGetLocalIssuerCertificate, + UnableToVerifyFirstCertificate, + CertificateRevoked, + InvalidCaCertificate, + PathLengthExceeded, + InvalidPurpose, + CertificateUntrusted, + CertificateRejected, + SubjectIssuerMismatch, // hostname mismatch? + AuthorityIssuerSerialNumberMismatch, + NoPeerCertificate, + HostNameMismatch, + NoSslSupport, + CertificateBlacklisted, + UnspecifiedError = -1 + }; + + // RVCT compiler in debug build does not like about default values in const- + // So as an workaround we define all constructor overloads here explicitly + QSslError(); + QSslError(SslError error); + QSslError(SslError error, const QSslCertificate &certificate); + + QSslError(const QSslError &other); + + ~QSslError(); + QSslError &operator=(const QSslError &other); + bool operator==(const QSslError &other) const; + inline bool operator!=(const QSslError &other) const + { return !(*this == other); } + + SslError error() const; + QString errorString() const; + QSslCertificate certificate() const; + +private: + QScopedPointer<QSslErrorPrivate> d; +}; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslError &error); +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslError::SslError &error); +#endif + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/ssl/qsslkey.cpp b/src/network/ssl/qsslkey.cpp new file mode 100644 index 0000000000..8b32f65405 --- /dev/null +++ b/src/network/ssl/qsslkey.cpp @@ -0,0 +1,460 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +/*! + \class QSslKey + \brief The QSslKey class provides an interface for private and public keys. + \since 4.3 + + \reentrant + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + QSslKey provides a simple API for managing keys. + + \sa QSslSocket, QSslCertificate, QSslCipher +*/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslkey.h" +#include "qsslkey_p.h" +#include "qsslsocket.h" +#include "qsslsocket_p.h" + +#include <QtCore/qatomic.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qiodevice.h> +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE +#endif + + +/*! + \internal + */ +void QSslKeyPrivate::clear(bool deep) +{ + isNull = true; + if (!QSslSocket::supportsSsl()) + return; + if (rsa) { + if (deep) + q_RSA_free(rsa); + rsa = 0; + } + if (dsa) { + if (deep) + q_DSA_free(dsa); + dsa = 0; + } +} + +/*! + \internal + + Allocates a new rsa or dsa struct and decodes \a pem into it + according to the current algorithm and type. + + If \a deepClear is true, the rsa/dsa struct is freed if it is was + already allocated, otherwise we "leak" memory (which is exactly + what we want for copy construction). + + If \a passPhrase is non-empty, it will be used for decrypting + \a pem. +*/ +void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase, + bool deepClear) +{ + if (pem.isEmpty()) + return; + + clear(deepClear); + + if (!QSslSocket::supportsSsl()) + return; + + BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pem.data()), pem.size()); + if (!bio) + return; + + void *phrase = (void *)passPhrase.constData(); + + if (algorithm == QSsl::Rsa) { + RSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_RSA_PUBKEY(bio, &rsa, 0, phrase) + : q_PEM_read_bio_RSAPrivateKey(bio, &rsa, 0, phrase); + if (rsa && rsa == result) + isNull = false; + } else { + DSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_DSA_PUBKEY(bio, &dsa, 0, phrase) + : q_PEM_read_bio_DSAPrivateKey(bio, &dsa, 0, phrase); + if (dsa && dsa == result) + isNull = false; + } + + q_BIO_free(bio); +} + +/*! + Constructs a null key. + + \sa isNull() +*/ +QSslKey::QSslKey() + : d(new QSslKeyPrivate) +{ +} + +/*! + \internal +*/ +QByteArray QSslKeyPrivate::pemHeader() const +{ + // ### use QByteArray::fromRawData() instead + if (type == QSsl::PublicKey) + return QByteArray("-----BEGIN PUBLIC KEY-----\n"); + else if (algorithm == QSsl::Rsa) + return QByteArray("-----BEGIN RSA PRIVATE KEY-----\n"); + return QByteArray("-----BEGIN DSA PRIVATE KEY-----\n"); +} + +/*! + \internal +*/ +QByteArray QSslKeyPrivate::pemFooter() const +{ + // ### use QByteArray::fromRawData() instead + if (type == QSsl::PublicKey) + return QByteArray("-----END PUBLIC KEY-----\n"); + else if (algorithm == QSsl::Rsa) + return QByteArray("-----END RSA PRIVATE KEY-----\n"); + return QByteArray("-----END DSA PRIVATE KEY-----\n"); +} + +/*! + \internal + + Returns a DER key formatted as PEM. +*/ +QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const +{ + QByteArray pem(der.toBase64()); + + const int lineWidth = 64; // RFC 1421 + const int newLines = pem.size() / lineWidth; + const bool rem = pem.size() % lineWidth; + + // ### optimize + for (int i = 0; i < newLines; ++i) + pem.insert((i + 1) * lineWidth + i, '\n'); + if (rem) + pem.append('\n'); // ### + + pem.prepend(pemHeader()); + pem.append(pemFooter()); + + return pem; +} + +/*! + \internal + + Returns a PEM key formatted as DER. +*/ +QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const +{ + const QByteArray header = pemHeader(); + const QByteArray footer = pemFooter(); + + QByteArray der(pem); + + const int headerIndex = der.indexOf(header); + const int footerIndex = der.indexOf(footer); + if (headerIndex == -1 || footerIndex == -1) + return QByteArray(); + + der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + + return QByteArray::fromBase64(der); // ignores newlines +} + +/*! + Constructs a QSslKey by decoding the string in the byte array + \a encoded using a specified \a algorithm and \a encoding format. + If the encoded key is encrypted, \a passPhrase is used to decrypt + it. \a type specifies whether the key is public or private. + + After construction, use isNull() to check if \a encoded contained + a valid key. +*/ +QSslKey::QSslKey(const QByteArray &encoded, QSsl::KeyAlgorithm algorithm, + QSsl::EncodingFormat encoding, QSsl::KeyType type, const QByteArray &passPhrase) + : d(new QSslKeyPrivate) +{ + d->type = type; + d->algorithm = algorithm; + d->decodePem((encoding == QSsl::Der) + ? d->pemFromDer(encoded) : encoded, + passPhrase); +} + +/*! + Constructs a QSslKey by reading and decoding data from a + \a device using a specified \a algorithm and \a encoding format. + If the encoded key is encrypted, \a passPhrase is used to decrypt + it. \a type specifies whether the key is public or private. + + After construction, use isNull() to check if \a device provided + a valid key. +*/ +QSslKey::QSslKey(QIODevice *device, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding, + QSsl::KeyType type, const QByteArray &passPhrase) + : d(new QSslKeyPrivate) +{ + QByteArray encoded; + if (device) + encoded = device->readAll(); + d->type = type; + d->algorithm = algorithm; + d->decodePem((encoding == QSsl::Der) ? + d->pemFromDer(encoded) : encoded, + passPhrase); +} + +/*! + Constructs an identical copy of \a other. +*/ +QSslKey::QSslKey(const QSslKey &other) : d(other.d) +{ +} + +/*! + Destroys the QSslKey object. +*/ +QSslKey::~QSslKey() +{ +} + +/*! + Copies the contents of \a other into this key, making the two keys + identical. + + Returns a reference to this QSslKey. +*/ +QSslKey &QSslKey::operator=(const QSslKey &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if this is a null key; otherwise false. + + \sa clear() +*/ +bool QSslKey::isNull() const +{ + return d->isNull; +} + +/*! + Clears the contents of this key, making it a null key. + + \sa isNull() +*/ +void QSslKey::clear() +{ + d = new QSslKeyPrivate; +} + +/*! + Returns the length of the key in bits, or -1 if the key is null. +*/ +int QSslKey::length() const +{ + if (d->isNull) + return -1; + return (d->algorithm == QSsl::Rsa) + ? q_BN_num_bits(d->rsa->n) : q_BN_num_bits(d->dsa->p); +} + +/*! + Returns the type of the key (i.e., PublicKey or PrivateKey). +*/ +QSsl::KeyType QSslKey::type() const +{ + return d->type; +} + +/*! + Returns the key algorithm. +*/ +QSsl::KeyAlgorithm QSslKey::algorithm() const +{ + return d->algorithm; +} + +/*! + Returns the key in DER encoding. The result is encrypted with + \a passPhrase if the key is a private key and \a passPhrase is + non-empty. +*/ +// ### autotest failure for non-empty passPhrase and private key +QByteArray QSslKey::toDer(const QByteArray &passPhrase) const +{ + if (d->isNull) + return QByteArray(); + return d->derFromPem(toPem(passPhrase)); +} + +/*! + Returns the key in PEM encoding. The result is encrypted with + \a passPhrase if the key is a private key and \a passPhrase is + non-empty. +*/ +QByteArray QSslKey::toPem(const QByteArray &passPhrase) const +{ + if (!QSslSocket::supportsSsl() || d->isNull) + return QByteArray(); + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return QByteArray(); + + bool fail = false; + + if (d->algorithm == QSsl::Rsa) { + if (d->type == QSsl::PublicKey) { + if (!q_PEM_write_bio_RSA_PUBKEY(bio, d->rsa)) + fail = true; + } else { + if (!q_PEM_write_bio_RSAPrivateKey( + bio, d->rsa, + // ### the cipher should be selectable in the API: + passPhrase.isEmpty() ? (const EVP_CIPHER *)0 : q_EVP_des_ede3_cbc(), + (uchar *)passPhrase.data(), passPhrase.size(), 0, 0)) { + fail = true; + } + } + } else { + if (d->type == QSsl::PublicKey) { + if (!q_PEM_write_bio_DSA_PUBKEY(bio, d->dsa)) + fail = true; + } else { + if (!q_PEM_write_bio_DSAPrivateKey( + bio, d->dsa, + // ### the cipher should be selectable in the API: + passPhrase.isEmpty() ? (const EVP_CIPHER *)0 : q_EVP_des_ede3_cbc(), + (uchar *)passPhrase.data(), passPhrase.size(), 0, 0)) { + fail = true; + } + } + } + + QByteArray pem; + if (!fail) { + char *data; + long size = q_BIO_get_mem_data(bio, &data); + pem = QByteArray(data, size); + } + q_BIO_free(bio); + return pem; +} + +/*! + Returns a pointer to the native key handle, if it is available; + otherwise a null pointer is returned. + + You can use this handle together with the native API to access + extended information about the key. + + \warning Use of this function has a high probability of being + non-portable, and its return value may vary across platforms, and + between minor Qt releases. +*/ +Qt::HANDLE QSslKey::handle() const +{ + return (d->algorithm == QSsl::Rsa) ? Qt::HANDLE(d->rsa) : Qt::HANDLE(d->dsa); +} + +/*! + Returns true if this key is equal to \a other; otherwise returns false. +*/ +bool QSslKey::operator==(const QSslKey &other) const +{ + if (isNull()) + return other.isNull(); + if (other.isNull()) + return isNull(); + if (algorithm() != other.algorithm()) + return false; + if (type() != other.type()) + return false; + if (length() != other.length()) + return false; + return toDer() == other.toDer(); +} + +/*! \fn bool QSslKey::operator!=(const QSslKey &other) const + + Returns true if this key is not equal to key \a other; otherwise + returns false. +*/ + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +QDebug operator<<(QDebug debug, const QSslKey &key) +{ + debug << "QSslKey(" + << (key.type() == QSsl::PublicKey ? "PublicKey" : "PrivateKey") + << ", " << (key.algorithm() == QSsl::Rsa ? "RSA" : "DSA") + << ", " << key.length() + << ')'; + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslkey.h b/src/network/ssl/qsslkey.h new file mode 100644 index 0000000000..89973042f9 --- /dev/null +++ b/src/network/ssl/qsslkey.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 QSSLKEY_H +#define QSSLKEY_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qsharedpointer.h> +#include <QtNetwork/qssl.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +template <typename A, typename B> struct QPair; + +class QIODevice; + +class QSslKeyPrivate; +class Q_NETWORK_EXPORT QSslKey +{ +public: + QSslKey(); + QSslKey(const QByteArray &encoded, QSsl::KeyAlgorithm algorithm, + QSsl::EncodingFormat format = QSsl::Pem, + QSsl::KeyType type = QSsl::PrivateKey, + const QByteArray &passPhrase = QByteArray()); + QSslKey(QIODevice *device, QSsl::KeyAlgorithm algorithm, + QSsl::EncodingFormat format = QSsl::Pem, + QSsl::KeyType type = QSsl::PrivateKey, + const QByteArray &passPhrase = QByteArray()); + QSslKey(const QSslKey &other); + ~QSslKey(); + QSslKey &operator=(const QSslKey &other); + + bool isNull() const; + void clear(); + + int length() const; + QSsl::KeyType type() const; + QSsl::KeyAlgorithm algorithm() const; + + QByteArray toPem(const QByteArray &passPhrase = QByteArray()) const; + QByteArray toDer(const QByteArray &passPhrase = QByteArray()) const; + + Qt::HANDLE handle() const; + + bool operator==(const QSslKey &key) const; + inline bool operator!=(const QSslKey &key) const { return !operator==(key); } + +private: + QExplicitlySharedDataPointer<QSslKeyPrivate> d; + friend class QSslCertificate; +}; + +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QSslKey &key); +#endif + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h new file mode 100644 index 0000000000..e476ecea8c --- /dev/null +++ b/src/network/ssl/qsslkey_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 QSSLKEY_P_H +#define QSSLKEY_P_H + +#include "qsslkey.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qsslcertificate.cpp. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include <openssl/rsa.h> +#include <openssl/dsa.h> + +QT_BEGIN_NAMESPACE + +class QSslKeyPrivate +{ +public: + inline QSslKeyPrivate() + : rsa(0) + , dsa(0) + { + clear(); + } + + inline ~QSslKeyPrivate() + { clear(); } + + void clear(bool deep = true); + + void decodePem(const QByteArray &pem, const QByteArray &passPhrase, + bool deepClear = true); + QByteArray pemHeader() const; + QByteArray pemFooter() const; + QByteArray pemFromDer(const QByteArray &der) const; + QByteArray derFromPem(const QByteArray &pem) const; + + bool isNull; + QSsl::KeyType type; + QSsl::KeyAlgorithm algorithm; + RSA *rsa; + DSA *dsa; + + QAtomicInt ref; + +private: + Q_DISABLE_COPY(QSslKeyPrivate) +}; + +QT_END_NAMESPACE + +#endif // QSSLKEY_P_H diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp new file mode 100644 index 0000000000..0dbf4b5196 --- /dev/null +++ b/src/network/ssl/qsslsocket.cpp @@ -0,0 +1,2260 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_DEBUG + +/*! + \class QSslSocket + \brief The QSslSocket class provides an SSL encrypted socket for both + clients and servers. + \since 4.3 + + \reentrant + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + QSslSocket establishes a secure, encrypted TCP connection you can + use for transmitting encrypted data. It can operate in both client + and server mode, and it supports modern SSL protocols, including + SSLv3 and TLSv1. By default, QSslSocket uses TLSv1, but you can + change the SSL protocol by calling setProtocol() as long as you do + it before the handshake has started. + + SSL encryption operates on top of the existing TCP stream after + the socket enters the ConnectedState. There are two simple ways to + establish a secure connection using QSslSocket: With an immediate + SSL handshake, or with a delayed SSL handshake occurring after the + connection has been established in unencrypted mode. + + The most common way to use QSslSocket is to construct an object + and start a secure connection by calling connectToHostEncrypted(). + This method starts an immediate SSL handshake once the connection + has been established. + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 0 + + As with a plain QTcpSocket, QSslSocket enters the HostLookupState, + ConnectingState, and finally the ConnectedState, if the connection + is successful. The handshake then starts automatically, and if it + succeeds, the encrypted() signal is emitted to indicate the socket + has entered the encrypted state and is ready for use. + + Note that data can be written to the socket immediately after the + return from connectToHostEncrypted() (i.e., before the encrypted() + signal is emitted). The data is queued in QSslSocket until after + the encrypted() signal is emitted. + + An example of using the delayed SSL handshake to secure an + existing connection is the case where an SSL server secures an + incoming connection. Suppose you create an SSL server class as a + subclass of QTcpServer. You would override + QTcpServer::incomingConnection() with something like the example + below, which first constructs an instance of QSslSocket and then + calls setSocketDescriptor() to set the new socket's descriptor to + the existing one passed in. It then initiates the SSL handshake + by calling startServerEncryption(). + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 1 + + If an error occurs, QSslSocket emits the sslErrors() signal. In this + case, if no action is taken to ignore the error(s), the connection + is dropped. To continue, despite the occurrence of an error, you + can call ignoreSslErrors(), either from within this slot after the + error occurs, or any time after construction of the QSslSocket and + before the connection is attempted. This will allow QSslSocket to + ignore the errors it encounters when establishing the identity of + the peer. Ignoring errors during an SSL handshake should be used + with caution, since a fundamental characteristic of secure + connections is that they should be established with a successful + handshake. + + Once encrypted, you use QSslSocket as a regular QTcpSocket. When + readyRead() is emitted, you can call read(), canReadLine() and + readLine(), or getChar() to read decrypted data from QSslSocket's + internal buffer, and you can call write() or putChar() to write + data back to the peer. QSslSocket will automatically encrypt the + written data for you, and emit encryptedBytesWritten() once + the data has been written to the peer. + + As a convenience, QSslSocket supports QTcpSocket's blocking + functions waitForConnected(), waitForReadyRead(), + waitForBytesWritten(), and waitForDisconnected(). It also provides + waitForEncrypted(), which will block the calling thread until an + encrypted connection has been established. + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 2 + + QSslSocket provides an extensive, easy-to-use API for handling + cryptographic ciphers, private keys, and local, peer, and + Certification Authority (CA) certificates. It also provides an API + for handling errors that occur during the handshake phase. + + The following features can also be customized: + + \list + \o The socket's cryptographic cipher suite can be customized before + the handshake phase with setCiphers() and setDefaultCiphers(). + \o The socket's local certificate and private key can be customized + before the handshake phase with setLocalCertificate() and + setPrivateKey(). + \o The CA certificate database can be extended and customized with + addCaCertificate(), addCaCertificates(), setCaCertificates(), + addDefaultCaCertificate(), addDefaultCaCertificates(), and + setDefaultCaCertificates(). + \endlist + + \note If available, root certificates on Unix (excluding Mac OS X) will be + loaded on demand from the standard certificate directories. If + you do not want to load root certificates on demand, you need to call either + the static function setDefaultCaCertificates() before the first SSL handshake + is made in your application, (e.g. via + "QSslSocket::setDefaultCaCertificates(QSslSocket::systemCaCertificates());"), + or call setCaCertificates() on your QSslSocket instance prior to the SSL + handshake. + + For more information about ciphers and certificates, refer to QSslCipher and + QSslCertificate. + + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit (\l{http://www.openssl.org/}). + + \note Be aware of the difference between the bytesWritten() signal and + the encryptedBytesWritten() signal. For a QTcpSocket, bytesWritten() + will get emitted as soon as data has been written to the TCP socket. + For a QSslSocket, bytesWritten() will get emitted when the data + is being encrypted and encryptedBytesWritten() + will get emitted as soon as data has been written to the TCP socket. + + \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 fail. + + Platform security capabilities are added via the + \l{qmake-variable-reference.html#target-capability}{TARGET.CAPABILITY} + qmake variable. + + \sa QSslCertificate, QSslCipher, QSslError +*/ + +/*! + \enum QSslSocket::SslMode + + Describes the connection modes available for QSslSocket. + + \value UnencryptedMode The socket is unencrypted. Its + behavior is identical to QTcpSocket. + + \value SslClientMode The socket is a client-side SSL socket. + It is either alreayd encrypted, or it is in the SSL handshake + phase (see QSslSocket::isEncrypted()). + + \value SslServerMode The socket is a server-side SSL socket. + It is either already encrypted, or it is in the SSL handshake + phase (see QSslSocket::isEncrypted()). +*/ + +/*! + \enum QSslSocket::PeerVerifyMode + \since 4.4 + + Describes the peer verification modes for QSslSocket. The default mode is + AutoVerifyPeer, which selects an appropriate mode depending on the + socket's QSocket::SslMode. + + \value VerifyNone QSslSocket will not request a certificate from the + peer. You can set this mode if you are not interested in the identity of + the other side of the connection. The connection will still be encrypted, + and your socket will still send its local certificate to the peer if it's + requested. + + \value QueryPeer QSslSocket will request a certificate from the peer, but + does not require this certificate to be valid. This is useful when you + want to display peer certificate details to the user without affecting the + actual SSL handshake. This mode is the default for servers. + + \value VerifyPeer QSslSocket will request a certificate from the peer + during the SSL handshake phase, and requires that this certificate is + valid. On failure, QSslSocket will emit the QSslSocket::sslErrors() + signal. This mode is the default for clients. + + \value AutoVerifyPeer QSslSocket will automatically use QueryPeer for + server sockets and VerifyPeer for client sockets. + + \sa QSslSocket::peerVerifyMode() +*/ + +/*! + \fn QSslSocket::encrypted() + + This signal is emitted when QSslSocket enters encrypted mode. After this + signal has been emitted, QSslSocket::isEncrypted() will return true, and + all further transmissions on the socket will be encrypted. + + \sa QSslSocket::connectToHostEncrypted(), QSslSocket::isEncrypted() +*/ + +/*! + \fn QSslSocket::modeChanged(QSslSocket::SslMode mode) + + This signal is emitted when QSslSocket changes from \l + QSslSocket::UnencryptedMode to either \l QSslSocket::SslClientMode or \l + QSslSocket::SslServerMode. \a mode is the new mode. + + \sa QSslSocket::mode() +*/ + +/*! + \fn QSslSocket::encryptedBytesWritten(qint64 written) + \since 4.4 + + This signal is emitted when QSslSocket writes its encrypted data to the + network. The \a written parameter contains the number of bytes that were + successfully written. + + \sa QIODevice::bytesWritten() +*/ + +/*! + \fn void QSslSocket::peerVerifyError(const QSslError &error) + \since 4.4 + + QSslSocket can emit this signal several times during the SSL handshake, + before encryption has been established, to indicate that an error has + occurred while establishing the identity of the peer. The \a error is + usually an indication that QSslSocket is unable to securely identify the + peer. + + This signal provides you with an early indication when something's wrong. + By connecting to this signal, you can manually choose to tear down the + connection from inside the connected slot before the handshake has + completed. If no action is taken, QSslSocket will proceed to emitting + QSslSocket::sslErrors(). + + \sa sslErrors() +*/ + +/*! + \fn void QSslSocket::sslErrors(const QList<QSslError> &errors); + + QSslSocket emits this signal after the SSL handshake to indicate that one + or more errors have occurred while establishing the identity of the + peer. The errors are usually an indication that QSslSocket is unable to + securely identify the peer. Unless any action is taken, the connection + will be dropped after this signal has been emitted. + + If you want to continue connecting despite the errors that have occurred, + you must call QSslSocket::ignoreSslErrors() from inside a slot connected to + this signal. If you need to access the error list at a later point, you + can call sslErrors() (without arguments). + + \a errors contains one or more errors that prevent QSslSocket from + verifying the identity of the peer. + + Note: You cannot use Qt::QueuedConnection when connecting to this signal, + or calling QSslSocket::ignoreSslErrors() will have no effect. + + \sa peerVerifyError() +*/ + +#include "qsslcipher.h" +#include "qsslsocket.h" +#include "qsslsocket_openssl_p.h" +#include "qsslconfiguration_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtCore/qmutex.h> +#include <QtCore/qelapsedtimer.h> +#include <QtNetwork/qhostaddress.h> +#include <QtNetwork/qhostinfo.h> + +QT_BEGIN_NAMESPACE + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +class QSslSocketGlobalData +{ +public: + QSslSocketGlobalData() : config(new QSslConfigurationPrivate) {} + + QMutex mutex; + QList<QSslCipher> supportedCiphers; + QExplicitlySharedDataPointer<QSslConfigurationPrivate> config; +}; +Q_GLOBAL_STATIC(QSslSocketGlobalData, globalData) + +/*! + Constructs a QSslSocket object. \a parent is passed to QObject's + constructor. The new socket's \l {QSslCipher} {cipher} suite is + set to the one returned by the static method defaultCiphers(). +*/ +QSslSocket::QSslSocket(QObject *parent) + : QTcpSocket(*new QSslSocketBackendPrivate, parent) +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::QSslSocket(" << parent << "), this =" << (void *)this; +#endif + d->q_ptr = this; + d->init(); +} + +/*! + Destroys the QSslSocket. +*/ +QSslSocket::~QSslSocket() +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::~QSslSocket(), this =" << (void *)this; +#endif + delete d->plainSocket; + d->plainSocket = 0; +} + +/*! + Starts an encrypted connection to the device \a hostName on \a + port, using \a mode as the \l OpenMode. This is equivalent to + calling connectToHost() to establish the connection, followed by a + call to startClientEncryption(). + + QSslSocket first enters the HostLookupState. Then, after entering + either the event loop or one of the waitFor...() functions, it + enters the ConnectingState, emits connected(), and then initiates + the SSL client handshake. At each state change, QSslSocket emits + signal stateChanged(). + + After initiating the SSL client handshake, if the identity of the + peer can't be established, signal sslErrors() is emitted. If you + want to ignore the errors and continue connecting, you must call + ignoreSslErrors(), either from inside a slot function connected to + the sslErrors() signal, or prior to entering encrypted mode. If + ignoreSslErrors() is not called, the connection is dropped, signal + disconnected() is emitted, and QSslSocket returns to the + UnconnectedState. + + If the SSL handshake is successful, QSslSocket emits encrypted(). + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 3 + + \bold{Note:} The example above shows that text can be written to + the socket immediately after requesting the encrypted connection, + before the encrypted() signal has been emitted. In such cases, the + text is queued in the object and written to the socket \e after + the connection is established and the encrypted() signal has been + emitted. + + The default for \a mode is \l ReadWrite. + + If you want to create a QSslSocket on the server side of a connection, you + should instead call startServerEncryption() upon receiving the incoming + connection through QTcpServer. + + \sa connectToHost(), startClientEncryption(), waitForConnected(), waitForEncrypted() +*/ +void QSslSocket::connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode mode) +{ + Q_D(QSslSocket); + if (d->state == ConnectedState || d->state == ConnectingState) { + qWarning("QSslSocket::connectToHostEncrypted() called when already connecting/connected"); + return; + } + + d->init(); + d->autoStartHandshake = true; + d->initialized = true; + + // Note: When connecting to localhost, some platforms (e.g., HP-UX and some BSDs) + // establish the connection immediately (i.e., first attempt). + connectToHost(hostName, port, mode); +} + +/*! + \since 4.6 + \overload + + In addition to the original behaviour of connectToHostEncrypted, + this overloaded method enables the usage of a different hostname + (\a sslPeerName) for the certificate validation instead of + the one used for the TCP connection (\a hostName). + + \sa connectToHostEncrypted() +*/ +void QSslSocket::connectToHostEncrypted(const QString &hostName, quint16 port, + const QString &sslPeerName, OpenMode mode) +{ + Q_D(QSslSocket); + if (d->state == ConnectedState || d->state == ConnectingState) { + qWarning("QSslSocket::connectToHostEncrypted() called when already connecting/connected"); + return; + } + + d->init(); + d->autoStartHandshake = true; + d->initialized = true; + d->verificationPeerName = sslPeerName; + + // Note: When connecting to localhost, some platforms (e.g., HP-UX and some BSDs) + // establish the connection immediately (i.e., first attempt). + connectToHost(hostName, port, mode); +} + +/*! + Initializes QSslSocket with the native socket descriptor \a + socketDescriptor. Returns true if \a socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. + The socket is opened in the mode specified by \a openMode, and + enters the socket state specified by \a state. + + \bold{Note:} It is not possible to initialize two sockets with the same + native socket descriptor. + + \sa socketDescriptor() +*/ +bool QSslSocket::setSocketDescriptor(int socketDescriptor, SocketState state, OpenMode openMode) +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::setSocketDescriptor(" << socketDescriptor << ',' + << state << ',' << openMode << ')'; +#endif + if (!d->plainSocket) + d->createPlainSocket(openMode); + bool retVal = d->plainSocket->setSocketDescriptor(socketDescriptor, state, openMode); + d->cachedSocketDescriptor = d->plainSocket->socketDescriptor(); + setSocketError(d->plainSocket->error()); + setSocketState(state); + setOpenMode(openMode); + setLocalPort(d->plainSocket->localPort()); + setLocalAddress(d->plainSocket->localAddress()); + setPeerPort(d->plainSocket->peerPort()); + setPeerAddress(d->plainSocket->peerAddress()); + setPeerName(d->plainSocket->peerName()); + return retVal; +} + +/*! + \since 4.6 + Sets the given \a option to the value described by \a value. + + \sa socketOption() +*/ +void QSslSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) +{ + Q_D(QSslSocket); + if (d->plainSocket) + d->plainSocket->setSocketOption(option, value); +} + +/*! + \since 4.6 + Returns the value of the \a option option. + + \sa setSocketOption() +*/ +QVariant QSslSocket::socketOption(QAbstractSocket::SocketOption option) +{ + Q_D(QSslSocket); + if (d->plainSocket) + return d->plainSocket->socketOption(option); + else + return QVariant(); +} + +/*! + Returns the current mode for the socket; either UnencryptedMode, where + QSslSocket behaves identially to QTcpSocket, or one of SslClientMode or + SslServerMode, where the client is either negotiating or in encrypted + mode. + + When the mode changes, QSslSocket emits modeChanged() + + \sa SslMode +*/ +QSslSocket::SslMode QSslSocket::mode() const +{ + Q_D(const QSslSocket); + return d->mode; +} + +/*! + Returns true if the socket is encrypted; otherwise, false is returned. + + An encrypted socket encrypts all data that is written by calling write() + or putChar() before the data is written to the network, and decrypts all + incoming data as the data is received from the network, before you call + read(), readLine() or getChar(). + + QSslSocket emits encrypted() when it enters encrypted mode. + + You can call sessionCipher() to find which cryptographic cipher is used to + encrypt and decrypt your data. + + \sa mode() +*/ +bool QSslSocket::isEncrypted() const +{ + Q_D(const QSslSocket); + return d->connectionEncrypted; +} + +/*! + Returns the socket's SSL protocol. By default, \l QSsl::SecureProtocols is used. + + \sa setProtocol() +*/ +QSsl::SslProtocol QSslSocket::protocol() const +{ + Q_D(const QSslSocket); + return d->configuration.protocol; +} + +/*! + Sets the socket's SSL protocol to \a protocol. This will affect the next + initiated handshake; calling this function on an already-encrypted socket + will not affect the socket's protocol. +*/ +void QSslSocket::setProtocol(QSsl::SslProtocol protocol) +{ + Q_D(QSslSocket); + d->configuration.protocol = protocol; +} + +/*! + \since 4.4 + + Returns the socket's verify mode. This mode mode decides whether + QSslSocket should request a certificate from the peer (i.e., the client + requests a certificate from the server, or a server requesting a + certificate from the client), and whether it should require that this + certificate is valid. + + The default mode is AutoVerifyPeer, which tells QSslSocket to use + VerifyPeer for clients and QueryPeer for servers. + + \sa setPeerVerifyMode(), peerVerifyDepth(), mode() +*/ +QSslSocket::PeerVerifyMode QSslSocket::peerVerifyMode() const +{ + Q_D(const QSslSocket); + return d->configuration.peerVerifyMode; +} + +/*! + \since 4.4 + + Sets the socket's verify mode to \a mode. This mode decides whether + QSslSocket should request a certificate from the peer (i.e., the client + requests a certificate from the server, or a server requesting a + certificate from the client), and whether it should require that this + certificate is valid. + + The default mode is AutoVerifyPeer, which tells QSslSocket to use + VerifyPeer for clients and QueryPeer for servers. + + Setting this mode after encryption has started has no effect on the + current connection. + + \sa peerVerifyMode(), setPeerVerifyDepth(), mode() +*/ +void QSslSocket::setPeerVerifyMode(QSslSocket::PeerVerifyMode mode) +{ + Q_D(QSslSocket); + d->configuration.peerVerifyMode = mode; +} + +/*! + \since 4.4 + + Returns the maximum number of certificates in the peer's certificate chain + to be checked during the SSL handshake phase, or 0 (the default) if no + maximum depth has been set, indicating that the whole certificate chain + should be checked. + + The certificates are checked in issuing order, starting with the peer's + own certificate, then its issuer's certificate, and so on. + + \sa setPeerVerifyDepth(), peerVerifyMode() +*/ +int QSslSocket::peerVerifyDepth() const +{ + Q_D(const QSslSocket); + return d->configuration.peerVerifyDepth; +} + +/*! + \since 4.4 + + Sets the maximum number of certificates in the peer's certificate chain to + be checked during the SSL handshake phase, to \a depth. Setting a depth of + 0 means that no maximum depth is set, indicating that the whole + certificate chain should be checked. + + The certificates are checked in issuing order, starting with the peer's + own certificate, then its issuer's certificate, and so on. + + \sa peerVerifyDepth(), setPeerVerifyMode() +*/ +void QSslSocket::setPeerVerifyDepth(int depth) +{ + Q_D(QSslSocket); + if (depth < 0) { + qWarning("QSslSocket::setPeerVerifyDepth: cannot set negative depth of %d", depth); + return; + } + d->configuration.peerVerifyDepth = depth; +} + +/*! + \since 4.8 + + Returns the different hostname for the certificate validation, as set by + setPeerVerifyName or by connectToHostEncrypted. + + \sa setPeerVerifyName(), connectToHostEncrypted() +*/ +QString QSslSocket::peerVerifyName() const +{ + Q_D(const QSslSocket); + return d->verificationPeerName; +} + +/*! + \since 4.8 + + Sets a different hostname for the certificate validation instead of the one used for the TCP + connection. + + \sa connectToHostEncrypted() +*/ +void QSslSocket::setPeerVerifyName(const QString &hostName) +{ + Q_D(QSslSocket); + d->verificationPeerName = hostName; +} + +/*! + \reimp + + Returns the number of decrypted bytes that are immediately available for + reading. +*/ +qint64 QSslSocket::bytesAvailable() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return QIODevice::bytesAvailable() + (d->plainSocket ? d->plainSocket->bytesAvailable() : 0); + return QIODevice::bytesAvailable() + d->readBuffer.size(); +} + +/*! + \reimp + + Returns the number of unencrypted bytes that are waiting to be encrypted + and written to the network. +*/ +qint64 QSslSocket::bytesToWrite() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return d->plainSocket ? d->plainSocket->bytesToWrite() : 0; + return d->writeBuffer.size(); +} + +/*! + \since 4.4 + + Returns the number of encrypted bytes that are awaiting decryption. + Normally, this function will return 0 because QSslSocket decrypts its + incoming data as soon as it can. +*/ +qint64 QSslSocket::encryptedBytesAvailable() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return 0; + return d->plainSocket->bytesAvailable(); +} + +/*! + \since 4.4 + + Returns the number of encrypted bytes that are waiting to be written to + the network. +*/ +qint64 QSslSocket::encryptedBytesToWrite() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return 0; + return d->plainSocket->bytesToWrite(); +} + +/*! + \reimp + + Returns true if you can read one while line (terminated by a single ASCII + '\n' character) of decrypted characters; otherwise, false is returned. +*/ +bool QSslSocket::canReadLine() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return QIODevice::canReadLine() || (d->plainSocket && d->plainSocket->canReadLine()); + return QIODevice::canReadLine() || (!d->readBuffer.isEmpty() && d->readBuffer.canReadLine()); +} + +/*! + \reimp +*/ +void QSslSocket::close() +{ +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::close()"; +#endif + Q_D(QSslSocket); + if (d->plainSocket) + d->plainSocket->close(); + QTcpSocket::close(); + + // must be cleared, reading/writing not possible on closed socket: + d->readBuffer.clear(); + d->writeBuffer.clear(); + // for QTcpSocket this is already done because it uses the readBuffer/writeBuffer + // if the QIODevice it is based on + // ### FIXME QSslSocket should probably do similar instead of having + // its own readBuffer/writeBuffer +} + +/*! + \reimp +*/ +bool QSslSocket::atEnd() const +{ + Q_D(const QSslSocket); + if (d->mode == UnencryptedMode) + return QIODevice::atEnd() && (!d->plainSocket || d->plainSocket->atEnd()); + return QIODevice::atEnd() && d->readBuffer.isEmpty(); +} + +/*! + This function writes as much as possible from the internal write buffer to + the underlying network socket, without blocking. If any data was written, + this function returns true; otherwise false is returned. + + Call this function if you need QSslSocket to start sending buffered data + immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QAbstractSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ +// Note! docs copied from QAbstractSocket::flush() +bool QSslSocket::flush() +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::flush()"; +#endif + if (d->mode != UnencryptedMode) + // encrypt any unencrypted bytes in our buffer + d->transmit(); + + return d->plainSocket ? d->plainSocket->flush() : false; +} + +/*! + \since 4.4 + + Sets the size of QSslSocket's internal read buffer to be \a size bytes. +*/ +void QSslSocket::setReadBufferSize(qint64 size) +{ + Q_D(QSslSocket); + d->readBufferMaxSize = size; + + if (d->plainSocket) + d->plainSocket->setReadBufferSize(size); +} + +/*! + Aborts the current connection and resets the socket. Unlike + disconnectFromHost(), this function immediately closes the socket, + clearing any pending data in the write buffer. + + \sa disconnectFromHost(), close() +*/ +void QSslSocket::abort() +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::abort()"; +#endif + if (d->plainSocket) + d->plainSocket->abort(); + close(); +} + +/*! + \since 4.4 + + Returns the socket's SSL configuration state. The default SSL + configuration of a socket is to use the default ciphers, + default CA certificates, no local private key or certificate. + + The SSL configuration also contains fields that can change with + time without notice. + + \sa localCertificate(), peerCertificate(), peerCertificateChain(), + sessionCipher(), privateKey(), ciphers(), caCertificates() +*/ +QSslConfiguration QSslSocket::sslConfiguration() const +{ + Q_D(const QSslSocket); + + // create a deep copy of our configuration + QSslConfigurationPrivate *copy = new QSslConfigurationPrivate(d->configuration); + copy->ref = 0; // the QSslConfiguration constructor refs up + copy->sessionCipher = d->sessionCipher(); + + return QSslConfiguration(copy); +} + +/*! + \since 4.4 + + Sets the socket's SSL configuration to be the contents of \a configuration. + This function sets the local certificate, the ciphers, the private key and the CA + certificates to those stored in \a configuration. + + It is not possible to set the SSL-state related fields. + + \sa setLocalCertificate(), setPrivateKey(), setCaCertificates(), setCiphers() +*/ +void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration) +{ + Q_D(QSslSocket); + d->configuration.localCertificate = configuration.localCertificate(); + d->configuration.privateKey = configuration.privateKey(); + d->configuration.ciphers = configuration.ciphers(); + d->configuration.caCertificates = configuration.caCertificates(); + d->configuration.peerVerifyDepth = configuration.peerVerifyDepth(); + d->configuration.peerVerifyMode = configuration.peerVerifyMode(); + d->configuration.protocol = configuration.protocol(); + d->allowRootCertOnDemandLoading = false; +} + +/*! + Sets the socket's local certificate to \a certificate. The local + certificate is necessary if you need to confirm your identity to the + peer. It is used together with the private key; if you set the local + certificate, you must also set the private key. + + The local certificate and private key are always necessary for server + sockets, but are also rarely used by client sockets if the server requires + the client to authenticate. + + \sa localCertificate(), setPrivateKey() +*/ +void QSslSocket::setLocalCertificate(const QSslCertificate &certificate) +{ + Q_D(QSslSocket); + d->configuration.localCertificate = certificate; +} + +/*! + \overload + + Sets the socket's local \l {QSslCertificate} {certificate} to the + first one found in file \a path, which is parsed according to the + specified \a format. +*/ +void QSslSocket::setLocalCertificate(const QString &path, + QSsl::EncodingFormat format) +{ + Q_D(QSslSocket); + QFile file(path); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + d->configuration.localCertificate = QSslCertificate(file.readAll(), format); +} + +/*! + Returns the socket's local \l {QSslCertificate} {certificate}, or + an empty certificate if no local certificate has been assigned. + + \sa setLocalCertificate(), privateKey() +*/ +QSslCertificate QSslSocket::localCertificate() const +{ + Q_D(const QSslSocket); + return d->configuration.localCertificate; +} + +/*! + Returns the peer's digital certificate (i.e., the immediate + certificate of the host you are connected to), or a null + certificate, if the peer has not assigned a certificate. + + The peer certificate is checked automatically during the + handshake phase, so this function is normally used to fetch + the certificate for display or for connection diagnostic + purposes. It contains information about the peer, including + its host name, the certificate issuer, and the peer's public + key. + + Because the peer certificate is set during the handshake phase, it + is safe to access the peer certificate from a slot connected to + the sslErrors() signal or the encrypted() signal. + + If a null certificate is returned, it can mean the SSL handshake + failed, or it can mean the host you are connected to doesn't have + a certificate, or it can mean there is no connection. + + If you want to check the peer's complete chain of certificates, + use peerCertificateChain() to get them all at once. + + \sa peerCertificateChain() +*/ +QSslCertificate QSslSocket::peerCertificate() const +{ + Q_D(const QSslSocket); + return d->configuration.peerCertificate; +} + +/*! + Returns the peer's chain of digital certificates, or an empty list + of certificates. + + Peer certificates are checked automatically during the handshake + phase. This function is normally used to fetch certificates for + display, or for performing connection diagnostics. Certificates + contain information about the peer and the certificate issuers, + including host name, issuer names, and issuer public keys. + + The peer certificates are set in QSslSocket during the handshake + phase, so it is safe to call this function from a slot connected + to the sslErrors() signal or the encrypted() signal. + + If an empty list is returned, it can mean the SSL handshake + failed, or it can mean the host you are connected to doesn't have + a certificate, or it can mean there is no connection. + + If you want to get only the peer's immediate certificate, use + peerCertificate(). + + \sa peerCertificate() +*/ +QList<QSslCertificate> QSslSocket::peerCertificateChain() const +{ + Q_D(const QSslSocket); + return d->configuration.peerCertificateChain; +} + +/*! + Returns the socket's cryptographic \l {QSslCipher} {cipher}, or a + null cipher if the connection isn't encrypted. The socket's cipher + for the session is set during the handshake phase. The cipher is + used to encrypt and decrypt data transmitted through the socket. + + QSslSocket also provides functions for setting the ordered list of + ciphers from which the handshake phase will eventually select the + session cipher. This ordered list must be in place before the + handshake phase begins. + + \sa ciphers(), setCiphers(), setDefaultCiphers(), defaultCiphers(), + supportedCiphers() +*/ +QSslCipher QSslSocket::sessionCipher() const +{ + Q_D(const QSslSocket); + return d->sessionCipher(); +} + +/*! + Sets the socket's private \l {QSslKey} {key} to \a key. The + private key and the local \l {QSslCertificate} {certificate} are + used by clients and servers that must prove their identity to + SSL peers. + + Both the key and the local certificate are required if you are + creating an SSL server socket. If you are creating an SSL client + socket, the key and local certificate are required if your client + must identify itself to an SSL server. + + \sa privateKey(), setLocalCertificate() +*/ +void QSslSocket::setPrivateKey(const QSslKey &key) +{ + Q_D(QSslSocket); + d->configuration.privateKey = key; +} + +/*! + \overload + + Reads the string in file \a fileName and decodes it using + a specified \a algorithm and encoding \a format to construct + an \l {QSslKey} {SSL key}. If the encoded key is encrypted, + \a passPhrase is used to decrypt it. + + The socket's private key is set to the constructed key. The + private key and the local \l {QSslCertificate} {certificate} are + used by clients and servers that must prove their identity to SSL + peers. + + Both the key and the local certificate are required if you are + creating an SSL server socket. If you are creating an SSL client + socket, the key and local certificate are required if your client + must identify itself to an SSL server. + + \sa privateKey(), setLocalCertificate() +*/ +void QSslSocket::setPrivateKey(const QString &fileName, QSsl::KeyAlgorithm algorithm, + QSsl::EncodingFormat format, const QByteArray &passPhrase) +{ + Q_D(QSslSocket); + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + d->configuration.privateKey = QSslKey(file.readAll(), algorithm, + format, QSsl::PrivateKey, passPhrase); + } +} + +/*! + Returns this socket's private key. + + \sa setPrivateKey(), localCertificate() +*/ +QSslKey QSslSocket::privateKey() const +{ + Q_D(const QSslSocket); + return d->configuration.privateKey; +} + +/*! + Returns this socket's current cryptographic cipher suite. This + list is used during the socket's handshake phase for choosing a + session cipher. The returned list of ciphers is ordered by + descending preference. (i.e., the first cipher in the list is the + most preferred cipher). The session cipher will be the first one + in the list that is also supported by the peer. + + By default, the handshake phase can choose any of the ciphers + supported by this system's SSL libraries, which may vary from + system to system. The list of ciphers supported by this system's + SSL libraries is returned by supportedCiphers(). You can restrict + the list of ciphers used for choosing the session cipher for this + socket by calling setCiphers() with a subset of the supported + ciphers. You can revert to using the entire set by calling + setCiphers() with the list returned by supportedCiphers(). + + You can restrict the list of ciphers used for choosing the session + cipher for \e all sockets by calling setDefaultCiphers() with a + subset of the supported ciphers. You can revert to using the + entire set by calling setCiphers() with the list returned by + supportedCiphers(). + + \sa setCiphers(), defaultCiphers(), setDefaultCiphers(), supportedCiphers() +*/ +QList<QSslCipher> QSslSocket::ciphers() const +{ + Q_D(const QSslSocket); + return d->configuration.ciphers; +} + +/*! + Sets the cryptographic cipher suite for this socket to \a ciphers, + which must contain a subset of the ciphers in the list returned by + supportedCiphers(). + + Restricting the cipher suite must be done before the handshake + phase, where the session cipher is chosen. + + \sa ciphers(), setDefaultCiphers(), supportedCiphers() +*/ +void QSslSocket::setCiphers(const QList<QSslCipher> &ciphers) +{ + Q_D(QSslSocket); + d->configuration.ciphers = ciphers; +} + +/*! + Sets the cryptographic cipher suite for this socket to \a ciphers, which + is a colon-separated list of cipher suite names. The ciphers are listed in + order of preference, starting with the most preferred cipher. For example: + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 4 + + Each cipher name in \a ciphers must be the name of a cipher in the + list returned by supportedCiphers(). Restricting the cipher suite + must be done before the handshake phase, where the session cipher + is chosen. + + \sa ciphers(), setDefaultCiphers(), supportedCiphers() +*/ +void QSslSocket::setCiphers(const QString &ciphers) +{ + Q_D(QSslSocket); + d->configuration.ciphers.clear(); + foreach (const QString &cipherName, ciphers.split(QLatin1String(":"),QString::SkipEmptyParts)) { + for (int i = 0; i < 3; ++i) { + // ### Crude + QSslCipher cipher(cipherName, QSsl::SslProtocol(i)); + if (!cipher.isNull()) + d->configuration.ciphers << cipher; + } + } +} + +/*! + Sets the default cryptographic cipher suite for all sockets in + this application to \a ciphers, which must contain a subset of the + ciphers in the list returned by supportedCiphers(). + + Restricting the default cipher suite only affects SSL sockets + that perform their handshake phase after the default cipher + suite has been changed. + + \sa setCiphers(), defaultCiphers(), supportedCiphers() +*/ +void QSslSocket::setDefaultCiphers(const QList<QSslCipher> &ciphers) +{ + QSslSocketPrivate::setDefaultCiphers(ciphers); +} + +/*! + Returns the default cryptographic cipher suite for all sockets in + this application. This list is used during the socket's handshake + phase when negotiating with the peer to choose a session cipher. + The list is ordered by preference (i.e., the first cipher in the + list is the most preferred cipher). + + By default, the handshake phase can choose any of the ciphers + supported by this system's SSL libraries, which may vary from + system to system. The list of ciphers supported by this system's + SSL libraries is returned by supportedCiphers(). + + \sa supportedCiphers() +*/ +QList<QSslCipher> QSslSocket::defaultCiphers() +{ + return QSslSocketPrivate::defaultCiphers(); +} + +/*! + Returns the list of cryptographic ciphers supported by this + system. This list is set by the system's SSL libraries and may + vary from system to system. + + \sa defaultCiphers(), ciphers(), setCiphers() +*/ +QList<QSslCipher> QSslSocket::supportedCiphers() +{ + return QSslSocketPrivate::supportedCiphers(); +} + +/*! + Searches all files in the \a path for certificates encoded in the + specified \a format and adds them to this socket's CA certificate + database. \a path can be explicit, or it can contain wildcards in + the format specified by \a syntax. Returns true if one or more + certificates are added to the socket's CA certificate database; + otherwise returns false. + + The CA certificate database is used by the socket during the + handshake phase to validate the peer's certificate. + + For more precise control, use addCaCertificate(). + + \sa addCaCertificate(), QSslCertificate::fromPath() +*/ +bool QSslSocket::addCaCertificates(const QString &path, QSsl::EncodingFormat format, + QRegExp::PatternSyntax syntax) +{ + Q_D(QSslSocket); + QList<QSslCertificate> certs = QSslCertificate::fromPath(path, format, syntax); + if (certs.isEmpty()) + return false; + + d->configuration.caCertificates += certs; + return true; +} + +/*! + Adds the \a certificate to this socket's CA certificate database. + The CA certificate database is used by the socket during the + handshake phase to validate the peer's certificate. + + To add multiple certificates, use addCaCertificates(). + + \sa caCertificates(), setCaCertificates() +*/ +void QSslSocket::addCaCertificate(const QSslCertificate &certificate) +{ + Q_D(QSslSocket); + d->configuration.caCertificates += certificate; +} + +/*! + Adds the \a certificates to this socket's CA certificate database. + The CA certificate database is used by the socket during the + handshake phase to validate the peer's certificate. + + For more precise control, use addCaCertificate(). + + \sa caCertificates(), addDefaultCaCertificate() +*/ +void QSslSocket::addCaCertificates(const QList<QSslCertificate> &certificates) +{ + Q_D(QSslSocket); + d->configuration.caCertificates += certificates; +} + +/*! + Sets this socket's CA certificate database to be \a certificates. + The certificate database must be set prior to the SSL handshake. + The CA certificate database is used by the socket during the + handshake phase to validate the peer's certificate. + + The CA certificate database can be reset to the current default CA + certificate database by calling this function with the list of CA + certificates returned by defaultCaCertificates(). + + \sa defaultCaCertificates() +*/ +void QSslSocket::setCaCertificates(const QList<QSslCertificate> &certificates) +{ + Q_D(QSslSocket); + d->configuration.caCertificates = certificates; + d->allowRootCertOnDemandLoading = false; +} + +/*! + Returns this socket's CA certificate database. The CA certificate + database is used by the socket during the handshake phase to + validate the peer's certificate. It can be moodified prior to the + handshake with addCaCertificate(), addCaCertificates(), and + setCaCertificates(). + + \note On Unix, this method may return an empty list if the root + certificates are loaded on demand. + + \sa addCaCertificate(), addCaCertificates(), setCaCertificates() +*/ +QList<QSslCertificate> QSslSocket::caCertificates() const +{ + Q_D(const QSslSocket); + return d->configuration.caCertificates; +} + +/*! + Searches all files in the \a path for certificates with the + specified \a encoding and adds them to the default CA certificate + database. \a path can be an explicit file, or it can contain + wildcards in the format specified by \a syntax. Returns true if + any CA certificates are added to the default database. + + Each SSL socket's CA certificate database is initialized to the + default CA certificate database. + + \sa defaultCaCertificates(), addCaCertificates(), addDefaultCaCertificate() +*/ +bool QSslSocket::addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat encoding, + QRegExp::PatternSyntax syntax) +{ + return QSslSocketPrivate::addDefaultCaCertificates(path, encoding, syntax); +} + +/*! + Adds \a certificate to the default CA certificate database. Each + SSL socket's CA certificate database is initialized to the default + CA certificate database. + + \sa defaultCaCertificates(), addCaCertificates() +*/ +void QSslSocket::addDefaultCaCertificate(const QSslCertificate &certificate) +{ + QSslSocketPrivate::addDefaultCaCertificate(certificate); +} + +/*! + Adds \a certificates to the default CA certificate database. Each + SSL socket's CA certificate database is initialized to the default + CA certificate database. + + \sa defaultCaCertificates(), addCaCertificates() +*/ +void QSslSocket::addDefaultCaCertificates(const QList<QSslCertificate> &certificates) +{ + QSslSocketPrivate::addDefaultCaCertificates(certificates); +} + +/*! + Sets the default CA certificate database to \a certificates. The + default CA certificate database is originally set to your system's + default CA certificate database. You can override the default CA + certificate database with your own CA certificate database using + this function. + + Each SSL socket's CA certificate database is initialized to the + default CA certificate database. + + \sa addDefaultCaCertificate() +*/ +void QSslSocket::setDefaultCaCertificates(const QList<QSslCertificate> &certificates) +{ + QSslSocketPrivate::setDefaultCaCertificates(certificates); +} + +/*! + Returns the current default CA certificate database. This database + is originally set to your system's default CA certificate database. + If no system default database is found, an empty database will be + returned. You can override the default CA certificate database + with your own CA certificate database using setDefaultCaCertificates(). + + Each SSL socket's CA certificate database is initialized to the + default CA certificate database. + + \note On Unix, this method may return an empty list if the root + certificates are loaded on demand. + + \sa caCertificates() +*/ +QList<QSslCertificate> QSslSocket::defaultCaCertificates() +{ + return QSslSocketPrivate::defaultCaCertificates(); +} + +/*! + This function provides the CA certificate database + provided by the operating system. The CA certificate database + returned by this function is used to initialize the database + returned by defaultCaCertificates(). You can replace that database + with your own with setDefaultCaCertificates(). + + \sa caCertificates(), defaultCaCertificates(), setDefaultCaCertificates() +*/ +QList<QSslCertificate> QSslSocket::systemCaCertificates() +{ + // we are calling ensureInitialized() in the method below + return QSslSocketPrivate::systemCaCertificates(); +} + +/*! + Waits until the socket is connected, or \a msecs milliseconds, + whichever happens first. If the connection has been established, + this function returns true; otherwise it returns false. + + \sa QAbstractSocket::waitForConnected() +*/ +bool QSslSocket::waitForConnected(int msecs) +{ + Q_D(QSslSocket); + if (!d->plainSocket) + return false; + bool retVal = d->plainSocket->waitForConnected(msecs); + if (!retVal) { + setSocketState(d->plainSocket->state()); + setSocketError(d->plainSocket->error()); + setErrorString(d->plainSocket->errorString()); + } + return retVal; +} + +/*! + Waits until the socket has completed the SSL handshake and has + emitted encrypted(), or \a msecs milliseconds, whichever comes + first. If encrypted() has been emitted, this function returns + true; otherwise (e.g., the socket is disconnected, or the SSL + handshake fails), false is returned. + + The following example waits up to one second for the socket to be + encrypted: + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 5 + + If msecs is -1, this function will not time out. + + \sa startClientEncryption(), startServerEncryption(), encrypted(), isEncrypted() +*/ +bool QSslSocket::waitForEncrypted(int msecs) +{ + Q_D(QSslSocket); + if (!d->plainSocket || d->connectionEncrypted) + return false; + if (d->mode == UnencryptedMode && !d->autoStartHandshake) + return false; + + QElapsedTimer stopWatch; + stopWatch.start(); + + if (d->plainSocket->state() != QAbstractSocket::ConnectedState) { + // Wait until we've entered connected state. + if (!d->plainSocket->waitForConnected(msecs)) + return false; + } + + while (!d->connectionEncrypted) { + // Start the handshake, if this hasn't been started yet. + if (d->mode == UnencryptedMode) + startClientEncryption(); + // Loop, waiting until the connection has been encrypted or an error + // occurs. + if (!d->plainSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) + return false; + } + return d->connectionEncrypted; +} + +/*! + \reimp +*/ +bool QSslSocket::waitForReadyRead(int msecs) +{ + Q_D(QSslSocket); + if (!d->plainSocket) + return false; + if (d->mode == UnencryptedMode && !d->autoStartHandshake) + return d->plainSocket->waitForReadyRead(msecs); + + // This function must return true if and only if readyRead() *was* emitted. + // So we initialize "readyReadEmitted" to false and check if it was set to true. + // waitForReadyRead() could be called recursively, so we can't use the same variable + // (the inner waitForReadyRead() may fail, but the outer one still succeeded) + bool readyReadEmitted = false; + bool *previousReadyReadEmittedPointer = d->readyReadEmittedPointer; + d->readyReadEmittedPointer = &readyReadEmitted; + + QElapsedTimer stopWatch; + stopWatch.start(); + + if (!d->connectionEncrypted) { + // Wait until we've entered encrypted mode, or until a failure occurs. + if (!waitForEncrypted(msecs)) { + d->readyReadEmittedPointer = previousReadyReadEmittedPointer; + return false; + } + } + + if (!d->writeBuffer.isEmpty()) { + // empty our cleartext write buffer first + d->transmit(); + } + + // test readyReadEmitted first because either operation above + // (waitForEncrypted or transmit) may have set it + while (!readyReadEmitted && + d->plainSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + } + + d->readyReadEmittedPointer = previousReadyReadEmittedPointer; + return readyReadEmitted; +} + +/*! + \reimp +*/ +bool QSslSocket::waitForBytesWritten(int msecs) +{ + Q_D(QSslSocket); + if (!d->plainSocket) + return false; + if (d->mode == UnencryptedMode) + return d->plainSocket->waitForBytesWritten(msecs); + + QElapsedTimer stopWatch; + stopWatch.start(); + + if (!d->connectionEncrypted) { + // Wait until we've entered encrypted mode, or until a failure occurs. + if (!waitForEncrypted(msecs)) + return false; + } + if (!d->writeBuffer.isEmpty()) { + // empty our cleartext write buffer first + d->transmit(); + } + + return d->plainSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); +} + +/*! + Waits until the socket has disconnected or \a msecs milliseconds, + whichever comes first. If the connection has been disconnected, + this function returns true; otherwise it returns false. + + \sa QAbstractSocket::waitForDisconnected() +*/ +bool QSslSocket::waitForDisconnected(int msecs) +{ + Q_D(QSslSocket); + + // require calling connectToHost() before waitForDisconnected() + if (state() == UnconnectedState) { + qWarning("QSslSocket::waitForDisconnected() is not allowed in UnconnectedState"); + return false; + } + + if (!d->plainSocket) + return false; + if (d->mode == UnencryptedMode) + return d->plainSocket->waitForDisconnected(msecs); + + QElapsedTimer stopWatch; + stopWatch.start(); + + if (!d->connectionEncrypted) { + // Wait until we've entered encrypted mode, or until a failure occurs. + if (!waitForEncrypted(msecs)) + return false; + } + bool retVal = d->plainSocket->waitForDisconnected(qt_timeout_value(msecs, stopWatch.elapsed())); + if (!retVal) { + setSocketState(d->plainSocket->state()); + setSocketError(d->plainSocket->error()); + setErrorString(d->plainSocket->errorString()); + } + return retVal; +} + +/*! + Returns a list of the last SSL errors that occurred. This is the + same list as QSslSocket passes via the sslErrors() signal. If the + connection has been encrypted with no errors, this function will + return an empty list. + + \sa connectToHostEncrypted() +*/ +QList<QSslError> QSslSocket::sslErrors() const +{ + Q_D(const QSslSocket); + return d->sslErrors; +} + +/*! + Returns true if this platform supports SSL; otherwise, returns + false. If the platform doesn't support SSL, the socket will fail + in the connection phase. +*/ +bool QSslSocket::supportsSsl() +{ + return QSslSocketPrivate::supportsSsl(); +} + +/*! + Starts a delayed SSL handshake for a client connection. This + function can be called when the socket is in the \l ConnectedState + but still in the \l UnencryptedMode. If it is not yet connected, + or if it is already encrypted, this function has no effect. + + Clients that implement STARTTLS functionality often make use of + delayed SSL handshakes. Most other clients can avoid calling this + function directly by using connectToHostEncrypted() instead, which + automatically performs the handshake. + + \sa connectToHostEncrypted(), startServerEncryption() +*/ +void QSslSocket::startClientEncryption() +{ + Q_D(QSslSocket); + if (d->mode != UnencryptedMode) { + qWarning("QSslSocket::startClientEncryption: cannot start handshake on non-plain connection"); + return; + } +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::startClientEncryption()"; +#endif + d->mode = SslClientMode; + emit modeChanged(d->mode); + d->startClientEncryption(); +} + +/*! + Starts a delayed SSL handshake for a server connection. This + function can be called when the socket is in the \l ConnectedState + but still in \l UnencryptedMode. If it is not connected or it is + already encrypted, the function has no effect. + + For server sockets, calling this function is the only way to + initiate the SSL handshake. Most servers will call this function + immediately upon receiving a connection, or as a result of having + received a protocol-specific command to enter SSL mode (e.g, the + server may respond to receiving the string "STARTTLS\r\n" by + calling this function). + + The most common way to implement an SSL server is to create a + subclass of QTcpServer and reimplement + QTcpServer::incomingConnection(). The returned socket descriptor + is then passed to QSslSocket::setSocketDescriptor(). + + \sa connectToHostEncrypted(), startClientEncryption() +*/ +void QSslSocket::startServerEncryption() +{ + Q_D(QSslSocket); + if (d->mode != UnencryptedMode) { + qWarning("QSslSocket::startServerEncryption: cannot start handshake on non-plain connection"); + return; + } +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::startServerEncryption()"; +#endif + d->mode = SslServerMode; + emit modeChanged(d->mode); + d->startServerEncryption(); +} + +/*! + This slot tells QSslSocket to ignore errors during QSslSocket's + handshake phase and continue connecting. If you want to continue + with the connection even if errors occur during the handshake + phase, then you must call this slot, either from a slot connected + to sslErrors(), or before the handshake phase. If you don't call + this slot, either in response to errors or before the handshake, + the connection will be dropped after the sslErrors() signal has + been emitted. + + If there are no errors during the SSL handshake phase (i.e., the + identity of the peer is established with no problems), QSslSocket + will not emit the sslErrors() signal, and it is unnecessary to + call this function. + + Ignoring errors that occur during an SSL handshake should be done + with caution. A fundamental characteristic of secure connections + is that they should be established with an error free handshake. + + \sa sslErrors() +*/ +void QSslSocket::ignoreSslErrors() +{ + Q_D(QSslSocket); + d->ignoreAllSslErrors = true; +} + +/*! + \overload + \since 4.6 + + This method tells QSslSocket to ignore only the errors given in \a + errors. + + Note that you can set the expected certificate in the SSL error: + If, for instance, you want to connect to a server that uses + a self-signed certificate, consider the following snippet: + + \snippet doc/src/snippets/code/src_network_ssl_qsslsocket.cpp 6 + + 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 sslErrors() +*/ +void QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) +{ + Q_D(QSslSocket); + d->ignoreErrorsList = errors; +} + +/*! + \internal +*/ +void QSslSocket::connectToHostImplementation(const QString &hostName, quint16 port, + OpenMode openMode) +{ + Q_D(QSslSocket); + if (!d->initialized) + d->init(); + d->initialized = false; + +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::connectToHostImplementation(" + << hostName << ',' << port << ',' << openMode << ')'; +#endif + if (!d->plainSocket) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "\tcreating internal plain socket"; +#endif + d->createPlainSocket(openMode); + } +#ifndef QT_NO_NETWORKPROXY + d->plainSocket->setProxy(proxy()); +#endif + QIODevice::open(openMode); + d->plainSocket->connectToHost(hostName, port, openMode); + d->cachedSocketDescriptor = d->plainSocket->socketDescriptor(); +} + +/*! + \internal +*/ +void QSslSocket::disconnectFromHostImplementation() +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::disconnectFromHostImplementation()"; +#endif + if (!d->plainSocket) + return; + if (d->state == UnconnectedState) + return; + if (d->mode == UnencryptedMode && !d->autoStartHandshake) { + d->plainSocket->disconnectFromHost(); + return; + } + if (d->state <= ConnectingState) { + d->pendingClose = true; + return; + } + + // Perhaps emit closing() + if (d->state != ClosingState) { + d->state = ClosingState; + emit stateChanged(d->state); + } + + if (!d->writeBuffer.isEmpty()) + return; + + if (d->mode == UnencryptedMode) { + d->plainSocket->disconnectFromHost(); + } else { + d->disconnectFromHost(); + } +} + +/*! + \reimp +*/ +qint64 QSslSocket::readData(char *data, qint64 maxlen) +{ + Q_D(QSslSocket); + qint64 readBytes = 0; + + if (d->mode == UnencryptedMode && !d->autoStartHandshake) { + readBytes = d->plainSocket->read(data, maxlen); + } else { + do { + const char *readPtr = d->readBuffer.readPointer(); + int bytesToRead = qMin<int>(maxlen - readBytes, d->readBuffer.nextDataBlockSize()); + ::memcpy(data + readBytes, readPtr, bytesToRead); + readBytes += bytesToRead; + d->readBuffer.free(bytesToRead); + } while (!d->readBuffer.isEmpty() && readBytes < maxlen); + } +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::readData(" << (void *)data << ',' << maxlen << ") ==" << readBytes; +#endif + + // possibly trigger another transmit() to decrypt more data from the socket + if (d->readBuffer.isEmpty() && d->plainSocket->bytesAvailable()) + QMetaObject::invokeMethod(this, "_q_flushReadBuffer", Qt::QueuedConnection); + + return readBytes; +} + +/*! + \reimp +*/ +qint64 QSslSocket::writeData(const char *data, qint64 len) +{ + Q_D(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::writeData(" << (void *)data << ',' << len << ')'; +#endif + if (d->mode == UnencryptedMode && !d->autoStartHandshake) + return d->plainSocket->write(data, len); + + char *writePtr = d->writeBuffer.reserve(len); + ::memcpy(writePtr, data, len); + + // make sure we flush to the plain socket's buffer + QMetaObject::invokeMethod(this, "_q_flushWriteBuffer", Qt::QueuedConnection); + + return len; +} + +/*! + \internal +*/ +QSslSocketPrivate::QSslSocketPrivate() + : initialized(false) + , mode(QSslSocket::UnencryptedMode) + , autoStartHandshake(false) + , connectionEncrypted(false) + , ignoreAllSslErrors(false) + , readyReadEmittedPointer(0) + , allowRootCertOnDemandLoading(true) + , plainSocket(0) +{ + QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration); +} + +/*! + \internal +*/ +QSslSocketPrivate::~QSslSocketPrivate() +{ +} + +/*! + \internal +*/ +void QSslSocketPrivate::init() +{ + mode = QSslSocket::UnencryptedMode; + autoStartHandshake = false; + connectionEncrypted = false; + ignoreAllSslErrors = false; + + // we don't want to clear the ignoreErrorsList, so + // that it is possible setting it before connecting +// ignoreErrorsList.clear(); + + readBuffer.clear(); + writeBuffer.clear(); + configuration.peerCertificate.clear(); + configuration.peerCertificateChain.clear(); +} + +/*! + \internal +*/ +QList<QSslCipher> QSslSocketPrivate::defaultCiphers() +{ + QMutexLocker locker(&globalData()->mutex); + return globalData()->config->ciphers; +} + +/*! + \internal +*/ +QList<QSslCipher> QSslSocketPrivate::supportedCiphers() +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + return globalData()->supportedCiphers; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setDefaultCiphers(const QList<QSslCipher> &ciphers) +{ + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->config->ciphers = ciphers; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setDefaultSupportedCiphers(const QList<QSslCipher> &ciphers) +{ + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->supportedCiphers = ciphers; +} + +/*! + \internal +*/ +QList<QSslCertificate> QSslSocketPrivate::defaultCaCertificates() +{ + // ### Qt5: rename everything containing "caCertificates" to "rootCertificates" or similar + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + return globalData()->config->caCertificates; +} + +/*! + \internal +*/ +void QSslSocketPrivate::setDefaultCaCertificates(const QList<QSslCertificate> &certs) +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->config->caCertificates = certs; + // when the certificates are set explicitly, we do not want to + // load the system certificates on demand + s_loadRootCertsOnDemand = false; +} + +/*! + \internal +*/ +bool QSslSocketPrivate::addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format, + QRegExp::PatternSyntax syntax) +{ + QSslSocketPrivate::ensureInitialized(); + QList<QSslCertificate> certs = QSslCertificate::fromPath(path, format, syntax); + if (certs.isEmpty()) + return false; + + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->config->caCertificates += certs; + return true; +} + +/*! + \internal +*/ +void QSslSocketPrivate::addDefaultCaCertificate(const QSslCertificate &cert) +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->config->caCertificates += cert; +} + +/*! + \internal +*/ +void QSslSocketPrivate::addDefaultCaCertificates(const QList<QSslCertificate> &certs) +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + globalData()->config.detach(); + globalData()->config->caCertificates += certs; +} + +/*! + \internal +*/ +QSslConfiguration QSslConfigurationPrivate::defaultConfiguration() +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + return QSslConfiguration(globalData()->config.data()); +} + +/*! + \internal +*/ +void QSslConfigurationPrivate::setDefaultConfiguration(const QSslConfiguration &configuration) +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + if (globalData()->config == configuration.d) + return; // nothing to do + + globalData()->config = const_cast<QSslConfigurationPrivate*>(configuration.d.constData()); +} + +/*! + \internal +*/ +void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPrivate *ptr) +{ + QSslSocketPrivate::ensureInitialized(); + QMutexLocker locker(&globalData()->mutex); + const QSslConfigurationPrivate *global = globalData()->config.constData(); + + if (!global) { + ptr = 0; + return; + } + + ptr->ref = 1; + ptr->peerCertificate = global->peerCertificate; + ptr->peerCertificateChain = global->peerCertificateChain; + ptr->localCertificate = global->localCertificate; + ptr->privateKey = global->privateKey; + ptr->sessionCipher = global->sessionCipher; + ptr->ciphers = global->ciphers; + ptr->caCertificates = global->caCertificates; + ptr->protocol = global->protocol; + ptr->peerVerifyMode = global->peerVerifyMode; + ptr->peerVerifyDepth = global->peerVerifyDepth; +} + +/*! + \internal +*/ +void QSslSocketPrivate::createPlainSocket(QIODevice::OpenMode openMode) +{ + Q_Q(QSslSocket); + q->setOpenMode(openMode); // <- from QIODevice + q->setSocketState(QAbstractSocket::UnconnectedState); + q->setSocketError(QAbstractSocket::UnknownSocketError); + q->setLocalPort(0); + q->setLocalAddress(QHostAddress()); + q->setPeerPort(0); + q->setPeerAddress(QHostAddress()); + q->setPeerName(QString()); + + plainSocket = new QTcpSocket(q); +#ifndef QT_NO_BEARERMANAGEMENT + //copy network session down to the plain socket (if it has been set) + plainSocket->setProperty("_q_networksession", q->property("_q_networksession")); +#endif + q->connect(plainSocket, SIGNAL(connected()), + q, SLOT(_q_connectedSlot()), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(hostFound()), + q, SLOT(_q_hostFoundSlot()), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(disconnected()), + q, SLOT(_q_disconnectedSlot()), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChangedSlot(QAbstractSocket::SocketState)), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_errorSlot(QAbstractSocket::SocketError)), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(readyRead()), + q, SLOT(_q_readyReadSlot()), + Qt::DirectConnection); + q->connect(plainSocket, SIGNAL(bytesWritten(qint64)), + q, SLOT(_q_bytesWrittenSlot(qint64)), + Qt::DirectConnection); +#ifndef QT_NO_NETWORKPROXY + q->connect(plainSocket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)), + q, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*))); +#endif + + readBuffer.clear(); + writeBuffer.clear(); + connectionEncrypted = false; + configuration.peerCertificate.clear(); + configuration.peerCertificateChain.clear(); + mode = QSslSocket::UnencryptedMode; + q->setReadBufferSize(readBufferMaxSize); +} + +void QSslSocketPrivate::pauseSocketNotifiers(QSslSocket *socket) +{ + if (!socket->d_func()->plainSocket) + return; + QAbstractSocketPrivate::pauseSocketNotifiers(socket->d_func()->plainSocket); +} + +void QSslSocketPrivate::resumeSocketNotifiers(QSslSocket *socket) +{ + if (!socket->d_func()->plainSocket) + return; + QAbstractSocketPrivate::resumeSocketNotifiers(socket->d_func()->plainSocket); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_connectedSlot() +{ + Q_Q(QSslSocket); + q->setLocalPort(plainSocket->localPort()); + q->setLocalAddress(plainSocket->localAddress()); + q->setPeerPort(plainSocket->peerPort()); + q->setPeerAddress(plainSocket->peerAddress()); + q->setPeerName(plainSocket->peerName()); + cachedSocketDescriptor = plainSocket->socketDescriptor(); + +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_connectedSlot()"; + qDebug() << "\tstate =" << q->state(); + qDebug() << "\tpeer =" << q->peerName() << q->peerAddress() << q->peerPort(); + qDebug() << "\tlocal =" << QHostInfo::fromName(q->localAddress().toString()).hostName() + << q->localAddress() << q->localPort(); +#endif + emit q->connected(); + + if (autoStartHandshake) { + q->startClientEncryption(); + } else if (pendingClose) { + pendingClose = false; + q->disconnectFromHost(); + } +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_hostFoundSlot() +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_hostFoundSlot()"; + qDebug() << "\tstate =" << q->state(); +#endif + emit q->hostFound(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_disconnectedSlot() +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_disconnectedSlot()"; + qDebug() << "\tstate =" << q->state(); +#endif + disconnected(); + emit q->disconnected(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_stateChangedSlot(QAbstractSocket::SocketState state) +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_stateChangedSlot(" << state << ')'; +#endif + q->setSocketState(state); + emit q->stateChanged(state); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_errorSlot(QAbstractSocket::SocketError error) +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_errorSlot(" << error << ')'; + qDebug() << "\tstate =" << q->state(); + qDebug() << "\terrorString =" << q->errorString(); +#endif + q->setSocketError(plainSocket->error()); + q->setErrorString(plainSocket->errorString()); + emit q->error(error); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_readyReadSlot() +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_readyReadSlot() -" << plainSocket->bytesAvailable() << "bytes available"; +#endif + if (mode == QSslSocket::UnencryptedMode) { + if (readyReadEmittedPointer) + *readyReadEmittedPointer = true; + emit q->readyRead(); + return; + } + + transmit(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_bytesWrittenSlot(qint64 written) +{ + Q_Q(QSslSocket); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocket::_q_bytesWrittenSlot(" << written << ')'; +#endif + + if (mode == QSslSocket::UnencryptedMode) + emit q->bytesWritten(written); + else + emit q->encryptedBytesWritten(written); + if (state == QAbstractSocket::ClosingState && writeBuffer.isEmpty()) + q->disconnectFromHost(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_flushWriteBuffer() +{ + Q_Q(QSslSocket); + if (!writeBuffer.isEmpty()) + q->flush(); +} + +/*! + \internal +*/ +void QSslSocketPrivate::_q_flushReadBuffer() +{ + // trigger a read from the plainSocket into SSL + if (mode != QSslSocket::UnencryptedMode) + transmit(); +} + +/*! + \internal +*/ +QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories() +{ + return QList<QByteArray>() << "/etc/ssl/certs/" // (K)ubuntu, OpenSUSE, Mandriva, MeeGo ... + << "/usr/lib/ssl/certs/" // Gentoo, Mandrake + << "/usr/share/ssl/" // Centos, Redhat, SuSE + << "/usr/local/ssl/" // Normal OpenSSL Tarball + << "/var/ssl/certs/" // AIX + << "/usr/local/ssl/certs/" // Solaris + << "/opt/openssl/certs/"; // HP-UX +} + +QT_END_NAMESPACE + +// For private slots +#define d d_ptr +#include "moc_qsslsocket.cpp" diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h new file mode 100644 index 0000000000..648fd8c1d0 --- /dev/null +++ b/src/network/ssl/qsslsocket.h @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_H +#define QSSLSOCKET_H + +#include <QtCore/qlist.h> +#include <QtCore/qregexp.h> +#ifndef QT_NO_OPENSSL +# include <QtNetwork/qtcpsocket.h> +# include <QtNetwork/qsslerror.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_OPENSSL + +class QDir; +class QSslCipher; +class QSslCertificate; +class QSslConfiguration; + +class QSslSocketPrivate; +class Q_NETWORK_EXPORT QSslSocket : public QTcpSocket +{ + Q_OBJECT +public: + enum SslMode { + UnencryptedMode, + SslClientMode, + SslServerMode + }; + + enum PeerVerifyMode { + VerifyNone, + QueryPeer, + VerifyPeer, + AutoVerifyPeer + }; + + QSslSocket(QObject *parent = 0); + ~QSslSocket(); + + // Autostarting the SSL client handshake. + void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void connectToHostEncrypted(const QString &hostName, quint16 port, const QString &sslPeerName, OpenMode mode = ReadWrite); + bool setSocketDescriptor(int socketDescriptor, SocketState state = ConnectedState, + OpenMode openMode = ReadWrite); + + // ### Qt 5: Make virtual + void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); + QVariant socketOption(QAbstractSocket::SocketOption option); + + SslMode mode() const; + bool isEncrypted() const; + + QSsl::SslProtocol protocol() const; + void setProtocol(QSsl::SslProtocol protocol); + + QSslSocket::PeerVerifyMode peerVerifyMode() const; + void setPeerVerifyMode(QSslSocket::PeerVerifyMode mode); + + int peerVerifyDepth() const; + void setPeerVerifyDepth(int depth); + + QString peerVerifyName() const; + void setPeerVerifyName(const QString &hostName); + + // From QIODevice + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + bool canReadLine() const; + void close(); + bool atEnd() const; + bool flush(); + void abort(); + + // From QAbstractSocket: + void setReadBufferSize(qint64 size); + + // Similar to QIODevice's: + qint64 encryptedBytesAvailable() const; + qint64 encryptedBytesToWrite() const; + + // SSL configuration + QSslConfiguration sslConfiguration() const; + void setSslConfiguration(const QSslConfiguration &config); + + // Certificate & cipher accessors. + void setLocalCertificate(const QSslCertificate &certificate); + void setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format = QSsl::Pem); + QSslCertificate localCertificate() const; + QSslCertificate peerCertificate() const; + QList<QSslCertificate> peerCertificateChain() const; + QSslCipher sessionCipher() const; + + // Private keys, for server sockets. + void setPrivateKey(const QSslKey &key); + void setPrivateKey(const QString &fileName, QSsl::KeyAlgorithm algorithm = QSsl::Rsa, + QSsl::EncodingFormat format = QSsl::Pem, + const QByteArray &passPhrase = QByteArray()); + QSslKey privateKey() const; + + // Cipher settings. + QList<QSslCipher> ciphers() const; + void setCiphers(const QList<QSslCipher> &ciphers); + void setCiphers(const QString &ciphers); + static void setDefaultCiphers(const QList<QSslCipher> &ciphers); + static QList<QSslCipher> defaultCiphers(); + static QList<QSslCipher> supportedCiphers(); + + // CA settings. + bool addCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem, + QRegExp::PatternSyntax syntax = QRegExp::FixedString); + void addCaCertificate(const QSslCertificate &certificate); + void addCaCertificates(const QList<QSslCertificate> &certificates); + void setCaCertificates(const QList<QSslCertificate> &certificates); + QList<QSslCertificate> caCertificates() const; + static bool addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem, + QRegExp::PatternSyntax syntax = QRegExp::FixedString); + static void addDefaultCaCertificate(const QSslCertificate &certificate); + static void addDefaultCaCertificates(const QList<QSslCertificate> &certificates); + static void setDefaultCaCertificates(const QList<QSslCertificate> &certificates); + static QList<QSslCertificate> defaultCaCertificates(); + static QList<QSslCertificate> systemCaCertificates(); + + bool waitForConnected(int msecs = 30000); + bool waitForEncrypted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + + QList<QSslError> sslErrors() const; + + static bool supportsSsl(); + void ignoreSslErrors(const QList<QSslError> &errors); + +public Q_SLOTS: + void startClientEncryption(); + void startServerEncryption(); + void ignoreSslErrors(); + +Q_SIGNALS: + void encrypted(); + void peerVerifyError(const QSslError &error); + void sslErrors(const QList<QSslError> &errors); + void modeChanged(QSslSocket::SslMode newMode); + void encryptedBytesWritten(qint64 totalBytes); + +protected Q_SLOTS: + void connectToHostImplementation(const QString &hostName, quint16 port, + OpenMode openMode); + void disconnectFromHostImplementation(); + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private: + Q_DECLARE_PRIVATE(QSslSocket) + Q_DISABLE_COPY(QSslSocket) + Q_PRIVATE_SLOT(d_func(), void _q_connectedSlot()) + Q_PRIVATE_SLOT(d_func(), void _q_hostFoundSlot()) + Q_PRIVATE_SLOT(d_func(), void _q_disconnectedSlot()) + Q_PRIVATE_SLOT(d_func(), void _q_stateChangedSlot(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_errorSlot(QAbstractSocket::SocketError)) + Q_PRIVATE_SLOT(d_func(), void _q_readyReadSlot()) + Q_PRIVATE_SLOT(d_func(), void _q_bytesWrittenSlot(qint64)) + Q_PRIVATE_SLOT(d_func(), void _q_flushWriteBuffer()) + Q_PRIVATE_SLOT(d_func(), void _q_flushReadBuffer()) + friend class QSslSocketBackendPrivate; +}; + +#endif // QT_NO_OPENSSL + +QT_END_NAMESPACE + +#ifndef QT_NO_OPENSSL +Q_DECLARE_METATYPE(QList<QSslError>) +#endif + +QT_END_HEADER + +#endif diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp new file mode 100644 index 0000000000..78a78a26f6 --- /dev/null +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -0,0 +1,1459 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_DEBUG + +#include "qsslsocket_openssl_p.h" +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslsocket.h" +#include "qsslcertificate_p.h" +#include "qsslcipher_p.h" + +#include <QtCore/qdatetime.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtCore/qdiriterator.h> +#include <QtCore/qelapsedtimer.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qmutex.h> +#include <QtCore/qthread.h> +#include <QtCore/qurl.h> +#include <QtCore/qvarlengtharray.h> +#include <QLibrary> // for loading the security lib for the CA store + +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +// Symbian does not seem to have the symbol for SNI defined +#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME +#define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 +#endif +#endif +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_MAC) +#define kSecTrustSettingsDomainSystem 2 // so we do not need to include the header file + PtrSecCertificateGetData QSslSocketPrivate::ptrSecCertificateGetData = 0; + PtrSecTrustSettingsCopyCertificates QSslSocketPrivate::ptrSecTrustSettingsCopyCertificates = 0; + PtrSecTrustCopyAnchorCertificates QSslSocketPrivate::ptrSecTrustCopyAnchorCertificates = 0; +#elif defined(Q_OS_WIN) + PtrCertOpenSystemStoreW QSslSocketPrivate::ptrCertOpenSystemStoreW = 0; + PtrCertFindCertificateInStore QSslSocketPrivate::ptrCertFindCertificateInStore = 0; + PtrCertCloseStore QSslSocketPrivate::ptrCertCloseStore = 0; +#elif defined(Q_OS_SYMBIAN) +#include <e32base.h> +#include <e32std.h> +#include <e32debug.h> +#include <QtCore/private/qcore_symbian_p.h> +#endif + +bool QSslSocketPrivate::s_libraryLoaded = false; +bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; +bool QSslSocketPrivate::s_loadRootCertsOnDemand = false; + +/* \internal + + From OpenSSL's thread(3) manual page: + + OpenSSL can safely be used in multi-threaded applications provided that at + least two callback functions are set. + + locking_function(int mode, int n, const char *file, int line) is needed to + perform locking on shared data structures. (Note that OpenSSL uses a + number of global data structures that will be implicitly shared + when-whenever ever multiple threads use OpenSSL.) Multi-threaded + applications will crash at random if it is not set. ... + ... + id_function(void) is a function that returns a thread ID. It is not + needed on Windows nor on platforms where getpid() returns a different + ID for each thread (most notably Linux) +*/ +class QOpenSslLocks +{ +public: + inline QOpenSslLocks() + : initLocker(QMutex::Recursive), + locksLocker(QMutex::Recursive) + { + QMutexLocker locker(&locksLocker); + int numLocks = q_CRYPTO_num_locks(); + locks = new QMutex *[numLocks]; + memset(locks, 0, numLocks * sizeof(QMutex *)); + } + inline ~QOpenSslLocks() + { + QMutexLocker locker(&locksLocker); + for (int i = 0; i < q_CRYPTO_num_locks(); ++i) + delete locks[i]; + delete [] locks; + + QSslSocketPrivate::deinitialize(); + } + inline QMutex *lock(int num) + { + QMutexLocker locker(&locksLocker); + QMutex *tmp = locks[num]; + if (!tmp) + tmp = locks[num] = new QMutex(QMutex::Recursive); + return tmp; + } + + QMutex *globalLock() + { + return &locksLocker; + } + + QMutex *initLock() + { + return &initLocker; + } + +private: + QMutex initLocker; + QMutex locksLocker; + QMutex **locks; +}; +Q_GLOBAL_STATIC(QOpenSslLocks, openssl_locks) + +extern "C" { +static void locking_function(int mode, int lockNumber, const char *, int) +{ + QMutex *mutex = openssl_locks()->lock(lockNumber); + + // Lock or unlock it + if (mode & CRYPTO_LOCK) + mutex->lock(); + else + mutex->unlock(); +} +static unsigned long id_function() +{ + return (quintptr)QThread::currentThreadId(); +} +} // extern "C" + +QSslSocketBackendPrivate::QSslSocketBackendPrivate() + : ssl(0), + ctx(0), + pkey(0), + readBio(0), + writeBio(0), + session(0) +{ + // Calls SSL_library_init(). + ensureInitialized(); +} + +QSslSocketBackendPrivate::~QSslSocketBackendPrivate() +{ +} + +QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher) +{ + QSslCipher ciph; + + char buf [256]; + QString descriptionOneLine = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf))); + + QStringList descriptionList = descriptionOneLine.split(QLatin1String(" "), QString::SkipEmptyParts); + if (descriptionList.size() > 5) { + // ### crude code. + ciph.d->isNull = false; + ciph.d->name = descriptionList.at(0); + + QString protoString = descriptionList.at(1); + ciph.d->protocolString = protoString; + ciph.d->protocol = QSsl::UnknownProtocol; + if (protoString == QLatin1String("SSLv3")) + ciph.d->protocol = QSsl::SslV3; + else if (protoString == QLatin1String("SSLv2")) + ciph.d->protocol = QSsl::SslV2; + else if (protoString == QLatin1String("TLSv1")) + ciph.d->protocol = QSsl::TlsV1; + + if (descriptionList.at(2).startsWith(QLatin1String("Kx="))) + ciph.d->keyExchangeMethod = descriptionList.at(2).mid(3); + if (descriptionList.at(3).startsWith(QLatin1String("Au="))) + ciph.d->authenticationMethod = descriptionList.at(3).mid(3); + if (descriptionList.at(4).startsWith(QLatin1String("Enc="))) + ciph.d->encryptionMethod = descriptionList.at(4).mid(4); + ciph.d->exportable = (descriptionList.size() > 6 && descriptionList.at(6) == QLatin1String("export")); + + ciph.d->bits = cipher->strength_bits; + ciph.d->supportedBits = cipher->alg_bits; + + } + return ciph; +} + +// ### This list is shared between all threads, and protected by a +// mutex. Investigate using thread local storage instead. +struct QSslErrorList +{ + QMutex mutex; + QList<QPair<int, int> > errors; +}; +Q_GLOBAL_STATIC(QSslErrorList, _q_sslErrorList) +static int q_X509Callback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + _q_sslErrorList()->errors << qMakePair<int, int>(ctx->error, ctx->error_depth); + } + // Always return OK to allow verification to continue. We're handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +bool QSslSocketBackendPrivate::initSslContext() +{ + Q_Q(QSslSocket); + + // Create and initialize SSL context. Accept SSLv2, SSLv3 and TLSv1. + bool client = (mode == QSslSocket::SslClientMode); + + bool reinitialized = false; +init_context: + switch (configuration.protocol) { + case QSsl::SslV2: + ctx = q_SSL_CTX_new(client ? q_SSLv2_client_method() : q_SSLv2_server_method()); + break; + case QSsl::SslV3: + ctx = q_SSL_CTX_new(client ? q_SSLv3_client_method() : q_SSLv3_server_method()); + break; + case QSsl::SecureProtocols: // SslV2 will be disabled below + case QSsl::TlsV1SslV3: // SslV2 will be disabled below + case QSsl::AnyProtocol: + default: + ctx = q_SSL_CTX_new(client ? q_SSLv23_client_method() : q_SSLv23_server_method()); + break; + case QSsl::TlsV1: + ctx = q_SSL_CTX_new(client ? q_TLSv1_client_method() : q_TLSv1_server_method()); + break; + } + if (!ctx) { + // After stopping Flash 10 the SSL library looses its ciphers. Try re-adding them + // by re-initializing the library. + if (!reinitialized) { + reinitialized = true; + if (q_SSL_library_init() == 1) + goto init_context; + } + + // ### Bad error code + q->setErrorString(QSslSocket::tr("Error creating SSL context (%1)").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Enable all bug workarounds. + if (configuration.protocol == QSsl::TlsV1SslV3 || configuration.protocol == QSsl::SecureProtocols) { + q_SSL_CTX_set_options(ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); + } else { + q_SSL_CTX_set_options(ctx, SSL_OP_ALL); + } + + // Initialize ciphers + QByteArray cipherString; + int first = true; + QList<QSslCipher> ciphers = configuration.ciphers; + if (ciphers.isEmpty()) + ciphers = defaultCiphers(); + foreach (const QSslCipher &cipher, ciphers) { + if (first) + first = false; + else + cipherString.append(':'); + cipherString.append(cipher.name().toLatin1()); + } + + if (!q_SSL_CTX_set_cipher_list(ctx, cipherString.data())) { + // ### Bad error code + q->setErrorString(QSslSocket::tr("Invalid or empty cipher list (%1)").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Add all our CAs to this store. + QList<QSslCertificate> expiredCerts; + foreach (const QSslCertificate &caCertificate, q->caCertificates()) { + // add expired certs later, so that the + // valid ones are used before the expired ones + if (! caCertificate.isValid()) { + expiredCerts.append(caCertificate); + } else { + q_X509_STORE_add_cert(ctx->cert_store, (X509 *)caCertificate.handle()); + } + } + + bool addExpiredCerts = true; +#if defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5) + //On Leopard SSL does not work if we add the expired certificates. + if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_5) + addExpiredCerts = false; +#endif + // now add the expired certs + if (addExpiredCerts) { + foreach (const QSslCertificate &caCertificate, expiredCerts) { + q_X509_STORE_add_cert(ctx->cert_store, (X509 *)caCertificate.handle()); + } + } + + if (s_loadRootCertsOnDemand && allowRootCertOnDemandLoading) { + // tell OpenSSL the directories where to look up the root certs on demand + QList<QByteArray> unixDirs = unixRootCertDirectories(); + for (int a = 0; a < unixDirs.count(); ++a) + q_SSL_CTX_load_verify_locations(ctx, 0, unixDirs.at(a).constData()); + } + + // Register a custom callback to get all verification errors. + X509_STORE_set_verify_cb_func(ctx->cert_store, q_X509Callback); + + if (!configuration.localCertificate.isNull()) { + // Require a private key as well. + if (configuration.privateKey.isNull()) { + q->setErrorString(QSslSocket::tr("Cannot provide a certificate with no key, %1").arg(getErrorsFromOpenSsl())); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Load certificate + if (!q_SSL_CTX_use_certificate(ctx, (X509 *)configuration.localCertificate.handle())) { + q->setErrorString(QSslSocket::tr("Error loading local certificate, %1").arg(getErrorsFromOpenSsl())); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Load private key + pkey = q_EVP_PKEY_new(); + // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free. + // this lead to a memory leak. Now we use the *_set1_* functions which do not + // take ownership of the RSA/DSA key instance because the QSslKey already has ownership. + if (configuration.privateKey.algorithm() == QSsl::Rsa) + q_EVP_PKEY_set1_RSA(pkey, (RSA *)configuration.privateKey.handle()); + else + q_EVP_PKEY_set1_DSA(pkey, (DSA *)configuration.privateKey.handle()); + if (!q_SSL_CTX_use_PrivateKey(ctx, pkey)) { + q->setErrorString(QSslSocket::tr("Error loading private key, %1").arg(getErrorsFromOpenSsl())); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Check if the certificate matches the private key. + if (!q_SSL_CTX_check_private_key(ctx)) { + q->setErrorString(QSslSocket::tr("Private key does not certify public key, %1").arg(getErrorsFromOpenSsl())); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + } + + // Initialize peer verification. + if (configuration.peerVerifyMode == QSslSocket::VerifyNone) { + q_SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0); + } else { + q_SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, q_X509Callback); + } + + // Set verification depth. + if (configuration.peerVerifyDepth != 0) + q_SSL_CTX_set_verify_depth(ctx, configuration.peerVerifyDepth); + + // Create and initialize SSL session + if (!(ssl = q_SSL_new(ctx))) { + // ### Bad error code + q->setErrorString(QSslSocket::tr("Error creating SSL session, %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) + if ((configuration.protocol == QSsl::TlsV1SslV3 || + configuration.protocol == QSsl::TlsV1 || + configuration.protocol == QSsl::SecureProtocols || + configuration.protocol == QSsl::AnyProtocol) && + client && q_SSLeay() >= 0x00090806fL) { + // Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format. + QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; + if (tlsHostName.isEmpty()) + tlsHostName = hostName; + QByteArray ace = QUrl::toAce(tlsHostName); + // only send the SNI header if the URL is valid and not an IP + if (!ace.isEmpty() && !QHostAddress().setAddress(tlsHostName)) { + if (!q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, ace.constData())) + qWarning("could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled"); + } + } +#endif + + // Clear the session. + q_SSL_clear(ssl); + errorList.clear(); + + // Initialize memory BIOs for encryption and decryption. + readBio = q_BIO_new(q_BIO_s_mem()); + writeBio = q_BIO_new(q_BIO_s_mem()); + if (!readBio || !writeBio) { + // ### Bad error code + q->setErrorString(QSslSocket::tr("Error creating SSL session: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return false; + } + + // Assign the bios. + q_SSL_set_bio(ssl, readBio, writeBio); + + if (mode == QSslSocket::SslClientMode) + q_SSL_set_connect_state(ssl); + else + q_SSL_set_accept_state(ssl); + + return true; +} + +/*! + \internal +*/ +void QSslSocketPrivate::deinitialize() +{ + q_CRYPTO_set_id_callback(0); + q_CRYPTO_set_locking_callback(0); +} + +/*! + \internal + + Does the minimum amount of initialization to determine whether SSL + is supported or not. +*/ + +bool QSslSocketPrivate::supportsSsl() +{ + return ensureLibraryLoaded(); +} + +bool QSslSocketPrivate::ensureLibraryLoaded() +{ + if (!q_resolveOpenSslSymbols()) + return false; + + // Check if the library itself needs to be initialized. + QMutexLocker locker(openssl_locks()->initLock()); + if (!s_libraryLoaded) { + s_libraryLoaded = true; + + // Initialize OpenSSL. + q_CRYPTO_set_id_callback(id_function); + q_CRYPTO_set_locking_callback(locking_function); + if (q_SSL_library_init() != 1) + return false; + q_SSL_load_error_strings(); + q_OpenSSL_add_all_algorithms(); + + // Initialize OpenSSL's random seed. + if (!q_RAND_status()) { + struct { + int msec; + int sec; + void *stack; + } randomish; + + int attempts = 500; + do { + if (attempts < 500) { +#ifdef Q_OS_UNIX + struct timespec ts = {0, 33333333}; + nanosleep(&ts, 0); +#else + Sleep(3); +#endif + randomish.msec = attempts; + } + randomish.stack = (void *)&randomish; + randomish.msec = QTime::currentTime().msec(); + randomish.sec = QTime::currentTime().second(); + q_RAND_seed((const char *)&randomish, sizeof(randomish)); + } while (!q_RAND_status() && --attempts); + if (!attempts) + return false; + } + } + return true; +} + +void QSslSocketPrivate::ensureCiphersAndCertsLoaded() +{ + QMutexLocker locker(openssl_locks()->initLock()); + if (s_loadedCiphersAndCerts) + return; + s_loadedCiphersAndCerts = true; + + resetDefaultCiphers(); + + //load symbols needed to receive certificates from system store +#if defined(Q_OS_MAC) + QLibrary securityLib("/System/Library/Frameworks/Security.framework/Versions/Current/Security"); + if (securityLib.load()) { + ptrSecCertificateGetData = (PtrSecCertificateGetData) securityLib.resolve("SecCertificateGetData"); + if (!ptrSecCertificateGetData) + qWarning("could not resolve symbols in security library"); // should never happen + + ptrSecTrustSettingsCopyCertificates = (PtrSecTrustSettingsCopyCertificates) securityLib.resolve("SecTrustSettingsCopyCertificates"); + if (!ptrSecTrustSettingsCopyCertificates) { // method was introduced in Leopard, use legacy method if it's not there + ptrSecTrustCopyAnchorCertificates = (PtrSecTrustCopyAnchorCertificates) securityLib.resolve("SecTrustCopyAnchorCertificates"); + if (!ptrSecTrustCopyAnchorCertificates) + qWarning("could not resolve symbols in security library"); // should never happen + } + } else { + qWarning("could not load security library"); + } +#elif defined(Q_OS_WIN) + HINSTANCE hLib = LoadLibraryW(L"Crypt32"); + if (hLib) { +#if defined(Q_OS_WINCE) + ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, L"CertOpenStore"); + ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, L"CertFindCertificateInStore"); + ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, L"CertCloseStore"); +#else + ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, "CertOpenSystemStoreW"); + ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, "CertFindCertificateInStore"); + ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, "CertCloseStore"); +#endif + if (!ptrCertOpenSystemStoreW || !ptrCertFindCertificateInStore || !ptrCertCloseStore) + qWarning("could not resolve symbols in crypt32 library"); // should never happen + } else { + qWarning("could not load crypt32 library"); // should never happen + } +#elif defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) && !defined(Q_OS_MAC) + // check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there) + QList<QByteArray> dirs = unixRootCertDirectories(); + QStringList symLinkFilter; + symLinkFilter << QLatin1String("[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"); + for (int a = 0; a < dirs.count(); ++a) { + QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files); + if (iterator.hasNext()) { + s_loadRootCertsOnDemand = true; + break; + } + } +#endif + // if on-demand loading was not enabled, load the certs now + if (!s_loadRootCertsOnDemand) + setDefaultCaCertificates(systemCaCertificates()); +} + +/*! + \internal + + Declared static in QSslSocketPrivate, makes sure the SSL libraries have + been initialized. +*/ + +void QSslSocketPrivate::ensureInitialized() +{ + if (!supportsSsl()) + return; + + ensureCiphersAndCertsLoaded(); +} + +/*! + \internal + + Declared static in QSslSocketPrivate, backend-dependent loading of + application-wide global ciphers. +*/ +void QSslSocketPrivate::resetDefaultCiphers() +{ + SSL_CTX *myCtx = q_SSL_CTX_new(q_SSLv23_client_method()); + SSL *mySsl = q_SSL_new(myCtx); + + QList<QSslCipher> ciphers; + + STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(mySsl); + for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { + if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { + if (cipher->valid) { + QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher); + if (!ciph.isNull()) { + if (!ciph.name().toLower().startsWith(QLatin1String("adh"))) + ciphers << ciph; + } + } + } + } + + q_SSL_CTX_free(myCtx); + q_SSL_free(mySsl); + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(ciphers); +} + +#if defined(Q_OS_SYMBIAN) + +CSymbianCertificateRetriever::CSymbianCertificateRetriever() : CActive(CActive::EPriorityStandard), + iCertificatePtr(0,0,0), iSequenceError(KErrNone) +{ +} + +CSymbianCertificateRetriever::~CSymbianCertificateRetriever() +{ + iThread.Close(); +} + +CSymbianCertificateRetriever* CSymbianCertificateRetriever::NewL() +{ + CSymbianCertificateRetriever* self = new (ELeave) CSymbianCertificateRetriever(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; +} + +int CSymbianCertificateRetriever::GetCertificates(QList<QByteArray> &certificates) +{ + iCertificates = &certificates; + + TRequestStatus status; + iThread.Logon(status); + iThread.Resume(); + User::WaitForRequest(status); + if (iThread.ExitType() == EExitKill) + return KErrDied; + else + return status.Int(); // Logon() completes with the thread's exit value +} + +void CSymbianCertificateRetriever::doThreadEntryL() +{ + CActiveScheduler* activeScheduler = new (ELeave) CActiveScheduler; + CleanupStack::PushL(activeScheduler); + CActiveScheduler::Install(activeScheduler); + + CActiveScheduler::Add(this); + + // These aren't deleted in the destructor so leaving the to CS is ok + iCertStore = CUnifiedCertStore::NewLC(qt_s60GetRFs(), EFalse); + iCertFilter = CCertAttributeFilter::NewLC(); + + // only interested in CA certs + iCertFilter->SetOwnerType(ECACertificate); + // only interested in X.509 format (we don't support WAP formats) + iCertFilter->SetFormat(EX509Certificate); + + // Kick off the sequence by initializing the cert store + iState = Initializing; + iCertStore->Initialize(iStatus); + SetActive(); + + CActiveScheduler::Start(); + + // Sequence complete, clean up + + // These MUST be cleaned up before the installed CActiveScheduler is destroyed and can't be left to the + // destructor of CSymbianCertificateRetriever. Otherwise the destructor of CActiveScheduler will get + // stuck. + iCertInfos.Close(); + CleanupStack::PopAndDestroy(3); // activeScheduler, iCertStore, iCertFilter +} + + +TInt CSymbianCertificateRetriever::ThreadEntryPoint(TAny* aParams) +{ + User::SetCritical(User::EProcessCritical); + CTrapCleanup* cleanupStack = CTrapCleanup::New(); + + CSymbianCertificateRetriever* self = (CSymbianCertificateRetriever*) aParams; + TRAPD(err, self->doThreadEntryL()); + delete cleanupStack; + + // doThreadEntryL() can leave only before the retrieval sequence is started + if (err) + return err; + else + return self->iSequenceError; // return any error that occurred during the retrieval +} + +void CSymbianCertificateRetriever::ConstructL() +{ + TInt err; + int i=0; + QString name(QLatin1String("CertWorkerThread-%1")); + //recently closed thread names remain in use for a while until all handles have been closed + //including users of RUndertaker + do { + err = iThread.Create(qt_QString2TPtrC(name.arg(i++)), + CSymbianCertificateRetriever::ThreadEntryPoint, 16384, NULL, this); + } while (err == KErrAlreadyExists); + User::LeaveIfError(err); +} + +void CSymbianCertificateRetriever::DoCancel() +{ + switch(iState) { + case Initializing: + iCertStore->CancelInitialize(); + break; + case Listing: + iCertStore->CancelList(); + break; + case RetrievingCertificates: + iCertStore->CancelGetCert(); + break; + } +} + +TInt CSymbianCertificateRetriever::RunError(TInt aError) +{ + // If something goes wrong in the sequence, abort the sequence + iSequenceError = aError; // this gets reported to the client in the TRequestStatus + CActiveScheduler::Stop(); + return KErrNone; +} + +void CSymbianCertificateRetriever::GetCertificateL() +{ + if (iCurrentCertIndex < iCertInfos.Count()) { + CCTCertInfo* certInfo = iCertInfos[iCurrentCertIndex++]; + iCertificateData = QByteArray(); + QT_TRYCATCH_LEAVING(iCertificateData.resize(certInfo->Size())); + iCertificatePtr.Set((TUint8*)iCertificateData.data(), 0, iCertificateData.size()); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "getting " << qt_TDesC2QString(certInfo->Label()) << " size=" << certInfo->Size(); + qDebug() << "format=" << certInfo->CertificateFormat(); + qDebug() << "ownertype=" << certInfo->CertificateOwnerType(); + qDebug() << "type=" << hex << certInfo->Type().iUid; +#endif + iCertStore->Retrieve(*certInfo, iCertificatePtr, iStatus); + iState = RetrievingCertificates; + SetActive(); + } else { + //reached end of list + CActiveScheduler::Stop(); + } +} + +void CSymbianCertificateRetriever::RunL() +{ +#ifdef QSSLSOCKET_DEBUG + qDebug() << "CSymbianCertificateRetriever::RunL status " << iStatus.Int() << " count " << iCertInfos.Count() << " index " << iCurrentCertIndex; +#endif + switch (iState) { + case Initializing: + User::LeaveIfError(iStatus.Int()); // initialise fail means pointless to continue + iState = Listing; + iCertStore->List(iCertInfos, *iCertFilter, iStatus); + SetActive(); + break; + + case Listing: + User::LeaveIfError(iStatus.Int()); // listing fail means pointless to continue + iCurrentCertIndex = 0; + GetCertificateL(); + break; + + case RetrievingCertificates: + if (iStatus.Int() == KErrNone) + iCertificates->append(iCertificateData); + else + qWarning() << "CSymbianCertificateRetriever: failed to retrieve a certificate, error " << iStatus.Int(); + GetCertificateL(); + break; + } +} +#endif // defined(Q_OS_SYMBIAN) + +QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates() +{ + ensureInitialized(); +#ifdef QSSLSOCKET_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + QList<QSslCertificate> systemCerts; +#if defined(Q_OS_MAC) + CFArrayRef cfCerts; + OSStatus status = 1; + + OSStatus SecCertificateGetData ( + SecCertificateRef certificate, + CSSM_DATA_PTR data + ); + + if (ptrSecCertificateGetData) { + if (ptrSecTrustSettingsCopyCertificates) + status = ptrSecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts); + else if (ptrSecTrustCopyAnchorCertificates) + status = ptrSecTrustCopyAnchorCertificates(&cfCerts); + if (!status) { + CFIndex size = CFArrayGetCount(cfCerts); + for (CFIndex i = 0; i < size; ++i) { + SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); + CSSM_DATA data; + CSSM_DATA_PTR dataPtr = &data; + if (ptrSecCertificateGetData(cfCert, dataPtr)) { + qWarning("error retrieving a CA certificate from the system store"); + } else { + int len = data.Length; + char *rawData = reinterpret_cast<char *>(data.Data); + QByteArray rawCert(rawData, len); + systemCerts.append(QSslCertificate::fromData(rawCert, QSsl::Der)); + } + } + CFRelease(cfCerts); + } + else { + // no detailed error handling here + qWarning("could not retrieve system CA certificates"); + } + } +#elif defined(Q_OS_WIN) + if (ptrCertOpenSystemStoreW && ptrCertFindCertificateInStore && ptrCertCloseStore) { + HCERTSTORE hSystemStore; +#if defined(Q_OS_WINCE) + hSystemStore = ptrCertOpenSystemStoreW(CERT_STORE_PROV_SYSTEM_W, + 0, + 0, + CERT_STORE_NO_CRYPT_RELEASE_FLAG|CERT_SYSTEM_STORE_CURRENT_USER, + L"ROOT"); +#else + hSystemStore = ptrCertOpenSystemStoreW(0, L"ROOT"); +#endif + if(hSystemStore) { + PCCERT_CONTEXT pc = NULL; + while(1) { + pc = ptrCertFindCertificateInStore( hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, pc); + if(!pc) + break; + QByteArray der((const char *)(pc->pbCertEncoded), static_cast<int>(pc->cbCertEncoded)); + QSslCertificate cert(der, QSsl::Der); + systemCerts.append(cert); + } + ptrCertCloseStore(hSystemStore, 0); + } + } +#elif defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) + QSet<QString> certFiles; + QList<QByteArray> directories = unixRootCertDirectories(); + QDir currentDir; + QStringList nameFilters; + nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt"); + currentDir.setNameFilters(nameFilters); + for (int a = 0; a < directories.count(); a++) { + currentDir.setPath(QLatin1String(directories.at(a))); + QDirIterator it(currentDir); + while(it.hasNext()) { + it.next(); + // use canonical path here to not load the same certificate twice if symlinked + certFiles.insert(it.fileInfo().canonicalFilePath()); + } + } + QSetIterator<QString> it(certFiles); + while(it.hasNext()) { + systemCerts.append(QSslCertificate::fromPath(it.next())); + } + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss + +#elif defined(Q_OS_SYMBIAN) + QList<QByteArray> certs; + QScopedPointer<CSymbianCertificateRetriever> retriever(CSymbianCertificateRetriever::NewL()); + + retriever->GetCertificates(certs); + foreach (const QByteArray &encodedCert, certs) { + QSslCertificate cert(encodedCert, QSsl::Der); + if (!cert.isNull()) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "imported certificate: " << cert.issuerInfo(QSslCertificate::CommonName); +#endif + systemCerts.append(cert); + } + } +#endif +#ifdef QSSLSOCKET_DEBUG + qDebug() << "systemCaCertificates retrieval time " << timer.elapsed() << "ms"; + qDebug() << "imported " << systemCerts.count() << " certificates"; +#endif + + return systemCerts; +} + +void QSslSocketBackendPrivate::startClientEncryption() +{ + if (!initSslContext()) { + // ### report error: internal OpenSSL failure + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +void QSslSocketBackendPrivate::startServerEncryption() +{ + if (!initSslContext()) { + // ### report error: internal OpenSSL failure + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +/*! + \internal + + Transmits encrypted data between the BIOs and the socket. +*/ +void QSslSocketBackendPrivate::transmit() +{ + Q_Q(QSslSocket); + + // If we don't have any SSL context, don't bother transmitting. + if (!ssl) + return; + + bool transmitting; + do { + transmitting = false; + + // If the connection is secure, we can transfer data from the write + // buffer (in plain text) to the write BIO through SSL_write. + if (connectionEncrypted && !writeBuffer.isEmpty()) { + qint64 totalBytesWritten = 0; + int nextDataBlockSize; + while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) { + int writtenBytes = q_SSL_write(ssl, writeBuffer.readPointer(), nextDataBlockSize); + if (writtenBytes <= 0) { + // ### Better error handling. + q->setErrorString(QSslSocket::tr("Unable to write data: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return; + } +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: encrypted" << writtenBytes << "bytes"; +#endif + writeBuffer.free(writtenBytes); + totalBytesWritten += writtenBytes; + + if (writtenBytes < nextDataBlockSize) { + // break out of the writing loop and try again after we had read + transmitting = true; + break; + } + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + } + } + + // Check if we've got any data to be written to the socket. + QVarLengthArray<char, 4096> data; + int pendingBytes; + while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0) { + // Read encrypted data from the write BIO into a buffer. + data.resize(pendingBytes); + int encryptedBytesRead = q_BIO_read(writeBio, data.data(), pendingBytes); + + // Write encrypted data from the buffer to the socket. + plainSocket->write(data.constData(), encryptedBytesRead); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: wrote" << encryptedBytesRead << "encrypted bytes to the socket"; +#endif + transmitting = true; + } + + // Check if we've got any data to be read from the socket. + if (!connectionEncrypted || !readBufferMaxSize || readBuffer.size() < readBufferMaxSize) + while ((pendingBytes = plainSocket->bytesAvailable()) > 0) { + // Read encrypted data from the socket into a buffer. + data.resize(pendingBytes); + // just peek() here because q_BIO_write could write less data than expected + int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket"; +#endif + // Write encrypted data from the buffer into the read BIO. + int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead); + + // do the actual read() here and throw away the results. + if (writtenToBio > 0) { + // ### TODO: make this cheaper by not making it memcpy. E.g. make it work with data=0x0 or make it work with seek + plainSocket->read(data.data(), writtenToBio); + } else { + // ### Better error handling. + q->setErrorString(QSslSocket::tr("Unable to decrypt data: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return; + } + + transmitting = true; + } + + // If the connection isn't secured yet, this is the time to retry the + // connect / accept. + if (!connectionEncrypted) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: testing encryption"; +#endif + if (startHandshake()) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: encryption established"; +#endif + connectionEncrypted = true; + transmitting = true; + } else if (plainSocket->state() != QAbstractSocket::ConnectedState) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: connection lost"; +#endif + break; + } else { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: encryption not done yet"; +#endif + } + } + + // If the request is small and the remote host closes the transmission + // after sending, there's a chance that startHandshake() will already + // have triggered a shutdown. + if (!ssl) + continue; + + // We always read everything from the SSL decryption buffers, even if + // we have a readBufferMaxSize. There's no point in leaving data there + // just so that readBuffer.size() == readBufferMaxSize. + int readBytes = 0; + data.resize(4096); + ::memset(data.data(), 0, data.size()); + do { + // Don't use SSL_pending(). It's very unreliable. + if ((readBytes = q_SSL_read(ssl, data.data(), data.size())) > 0) { +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: decrypted" << readBytes << "bytes"; +#endif + char *ptr = readBuffer.reserve(readBytes); + ::memcpy(ptr, data.data(), readBytes); + + if (readyReadEmittedPointer) + *readyReadEmittedPointer = true; + emit q->readyRead(); + transmitting = true; + continue; + } + + // Error. + switch (q_SSL_get_error(ssl, readBytes)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // Out of data. + break; + case SSL_ERROR_ZERO_RETURN: + // The remote host closed the connection. +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::transmit: remote disconnect"; +#endif + plainSocket->disconnectFromHost(); + break; + case SSL_ERROR_SYSCALL: // some IO error + case SSL_ERROR_SSL: // error in the SSL library + // we do not know exactly what the error is, nor whether we can recover from it, + // so just return to prevent an endless loop in the outer "while" statement + q->setErrorString(QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + return; + default: + // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a + // BIO_s_connect() or BIO_s_accept(), which we do not call. + // SSL_ERROR_WANT_X509_LOOKUP: can only happen with a + // SSL_CTX_set_client_cert_cb(), which we do not call. + // So this default case should never be triggered. + q->setErrorString(QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::UnknownSocketError); + emit q->error(QAbstractSocket::UnknownSocketError); + break; + } + } while (ssl && readBytes > 0); + } while (ssl && ctx && transmitting); +} + +static QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert) +{ + QSslError error; + switch (errorCode) { + case X509_V_OK: + // X509_V_OK is also reported if the peer had no certificate. + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break; + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break; + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + error = QSslError(QSslError::CertificateSignatureFailed, cert); break; + case X509_V_ERR_CERT_NOT_YET_VALID: + error = QSslError(QSslError::CertificateNotYetValid, cert); break; + case X509_V_ERR_CERT_HAS_EXPIRED: + error = QSslError(QSslError::CertificateExpired, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + error = QSslError(QSslError::InvalidNotBeforeField, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + error = QSslError(QSslError::InvalidNotAfterField, cert); break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + error = QSslError(QSslError::SelfSignedCertificate, cert); break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break; + case X509_V_ERR_CERT_REVOKED: + error = QSslError(QSslError::CertificateRevoked, cert); break; + case X509_V_ERR_INVALID_CA: + error = QSslError(QSslError::InvalidCaCertificate, cert); break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + error = QSslError(QSslError::PathLengthExceeded, cert); break; + case X509_V_ERR_INVALID_PURPOSE: + error = QSslError(QSslError::InvalidPurpose, cert); break; + case X509_V_ERR_CERT_UNTRUSTED: + error = QSslError(QSslError::CertificateUntrusted, cert); break; + case X509_V_ERR_CERT_REJECTED: + error = QSslError(QSslError::CertificateRejected, cert); break; + default: + error = QSslError(QSslError::UnspecifiedError, cert); break; + } + return error; +} + +bool QSslSocketBackendPrivate::startHandshake() +{ + Q_Q(QSslSocket); + + // Check if the connection has been established. Get all errors from the + // verification stage. + _q_sslErrorList()->mutex.lock(); + _q_sslErrorList()->errors.clear(); + int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl); + + const QList<QPair<int, int> > &lastErrors = _q_sslErrorList()->errors; + for (int i = 0; i < lastErrors.size(); ++i) { + const QPair<int, int> ¤tError = lastErrors.at(i); + // Initialize the peer certificate chain in order to find which certificate caused this error + if (configuration.peerCertificateChain.isEmpty()) + configuration.peerCertificateChain = STACKOFX509_to_QSslCertificates(q_SSL_get_peer_cert_chain(ssl)); + emit q->peerVerifyError(_q_OpenSSL_to_QSslError(currentError.first, + configuration.peerCertificateChain.value(currentError.second))); + if (q->state() != QAbstractSocket::ConnectedState) + break; + } + + errorList << lastErrors; + _q_sslErrorList()->mutex.unlock(); + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) + return false; + + // Check if we're encrypted or not. + if (result <= 0) { + switch (q_SSL_get_error(ssl, result)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // The handshake is not yet complete. + break; + default: + q->setErrorString(QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl())); + q->setSocketError(QAbstractSocket::SslHandshakeFailedError); +#ifdef QSSLSOCKET_DEBUG + qDebug() << "QSslSocketBackendPrivate::startHandshake: error!" << q->errorString(); +#endif + emit q->error(QAbstractSocket::SslHandshakeFailedError); + q->abort(); + } + return false; + } + + // Store the peer certificate and chain. For clients, the peer certificate + // chain includes the peer certificate; for servers, it doesn't. Both the + // peer certificate and the chain may be empty if the peer didn't present + // any certificate. + if (configuration.peerCertificateChain.isEmpty()) + configuration.peerCertificateChain = STACKOFX509_to_QSslCertificates(q_SSL_get_peer_cert_chain(ssl)); + X509 *x509 = q_SSL_get_peer_certificate(ssl); + configuration.peerCertificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509); + q_X509_free(x509); + + // Start translating errors. + QList<QSslError> errors; + + if (QSslCertificatePrivate::isBlacklisted(configuration.peerCertificate)) { + QSslError error(QSslError::CertificateBlacklisted, configuration.peerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + if (!configuration.peerCertificate.isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + if (mode == QSslSocket::SslClientMode) { + QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); + QString commonName = configuration.peerCertificate.subjectInfo(QSslCertificate::CommonName); + + if (!isMatchingHostname(commonName.toLower(), peerName.toLower())) { + bool matched = false; + foreach (const QString &altName, configuration.peerCertificate + .alternateSubjectNames().values(QSsl::DnsEntry)) { + if (isMatchingHostname(altName.toLower(), peerName.toLower())) { + matched = true; + break; + } + } + + if (!matched) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } + } else { + // No peer certificate presented. Report as error if the socket + // expected one. + if (doVerifyPeer) { + QSslError error(QSslError::NoPeerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // Translate errors from the error list into QSslErrors. + for (int i = 0; i < errorList.size(); ++i) { + const QPair<int, int> &errorAndDepth = errorList.at(i); + int err = errorAndDepth.first; + int depth = errorAndDepth.second; + errors << _q_OpenSSL_to_QSslError(err, configuration.peerCertificateChain.value(depth)); + } + + if (!errors.isEmpty()) { + sslErrors = errors; + emit q->sslErrors(errors); + + bool doEmitSslError; + if (!ignoreErrorsList.empty()) { + // check whether the errors we got are all in the list of expected errors + // (applies only if the method QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) + // was called) + doEmitSslError = false; + for (int a = 0; a < errors.count(); a++) { + if (!ignoreErrorsList.contains(errors.at(a))) { + doEmitSslError = true; + break; + } + } + } else { + // if QSslSocket::ignoreSslErrors(const QList<QSslError> &errors) was not called and + // we get an SSL error, emit a signal unless we ignored all errors (by calling + // QSslSocket::ignoreSslErrors() ) + doEmitSslError = !ignoreAllSslErrors; + } + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + q->setErrorString(sslErrors.first().errorString()); + q->setSocketError(QAbstractSocket::SslHandshakeFailedError); + emit q->error(QAbstractSocket::SslHandshakeFailedError); + plainSocket->disconnectFromHost(); + return false; + } + } else { + sslErrors.clear(); + } + + // if we have a max read buffer size, reset the plain socket's to 1k + if (readBufferMaxSize) + plainSocket->setReadBufferSize(1024); + + connectionEncrypted = true; + emit q->encrypted(); + if (autoStartHandshake && pendingClose) { + pendingClose = false; + q->disconnectFromHost(); + } + return true; +} + +void QSslSocketBackendPrivate::disconnectFromHost() +{ + if (ssl) { + q_SSL_shutdown(ssl); + transmit(); + } + plainSocket->disconnectFromHost(); +} + +void QSslSocketBackendPrivate::disconnected() +{ + if (ssl) { + q_SSL_free(ssl); + ssl = 0; + } + if (ctx) { + q_SSL_CTX_free(ctx); + ctx = 0; + } + if (pkey) { + q_EVP_PKEY_free(pkey); + pkey = 0; + } + +} + +QSslCipher QSslSocketBackendPrivate::sessionCipher() const +{ + if (!ssl || !ctx) + return QSslCipher(); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + // FIXME This is fairly evil, but needed to keep source level compatibility + // with the OpenSSL 0.9.x implementation at maximum -- some other functions + // don't take a const SSL_CIPHER* when they should + SSL_CIPHER *sessionCipher = const_cast<SSL_CIPHER *>(q_SSL_get_current_cipher(ssl)); +#else + SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); +#endif + return sessionCipher ? QSslCipher_from_SSL_CIPHER(sessionCipher) : QSslCipher(); +} + +QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509) +{ + ensureInitialized(); + QList<QSslCertificate> certificates; + for (int i = 0; i < q_sk_X509_num(x509); ++i) { + if (X509 *entry = q_sk_X509_value(x509, i)) + certificates << QSslCertificatePrivate::QSslCertificate_from_X509(entry); + } + return certificates; +} + +QString QSslSocketBackendPrivate::getErrorsFromOpenSsl() +{ + QString errorString; + unsigned long errNum; + while((errNum = q_ERR_get_error())) { + if (! errorString.isEmpty()) + errorString.append(QLatin1String(", ")); + const char *error = q_ERR_error_string(errNum, NULL); + errorString.append(QString::fromAscii(error)); // error is ascii according to man ERR_error_string + } + return errorString; +} + +bool QSslSocketBackendPrivate::isMatchingHostname(const QString &cn, const QString &hostname) +{ + int wildcard = cn.indexOf(QLatin1Char('*')); + + // Check this is a wildcard cert, if not then just compare the strings + if (wildcard < 0) + return cn == hostname; + + int firstCnDot = cn.indexOf(QLatin1Char('.')); + int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1); + + // Check at least 3 components + if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length())) + return false; + + // Check * is last character of 1st component (ie. there's a following .) + if (wildcard+1 != firstCnDot) + return false; + + // Check only one star + if (cn.lastIndexOf(QLatin1Char('*')) != wildcard) + return false; + + // Check characters preceding * (if any) match + if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard))) + return false; + + // Check characters following first . match + if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot)) + return false; + + // Check if the hostname is an IP address, if so then wildcards are not allowed + QHostAddress addr(hostname); + if (!addr.isNull()) + return false; + + // Ok, I guess this was a wildcard CN and the hostname matches. + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h new file mode 100644 index 0000000000..ca49fabc13 --- /dev/null +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_OPENSSL_P_H +#define QSSLSOCKET_OPENSSL_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qsslsocket_p.h" + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#if defined(OCSP_RESPONSE) +#undef OCSP_RESPONSE +#endif +#endif + +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/pkcs7.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> +#include <openssl/stack.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> +#include <openssl/dsa.h> +#include <openssl/rsa.h> +#include <openssl/crypto.h> +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +#include <openssl/tls1.h> +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +typedef _STACK STACK; +#endif + +QT_BEGIN_NAMESPACE + +class QSslSocketBackendPrivate : public QSslSocketPrivate +{ + Q_DECLARE_PUBLIC(QSslSocket) +public: + QSslSocketBackendPrivate(); + virtual ~QSslSocketBackendPrivate(); + + // SSL context + bool initSslContext(); + SSL *ssl; + SSL_CTX *ctx; + EVP_PKEY *pkey; + BIO *readBio; + BIO *writeBio; + SSL_SESSION *session; + X509_STORE *certificateStore; + X509_STORE_CTX *certificateStoreCtx; + QList<QPair<int, int> > errorList; + + // Platform specific functions + void startClientEncryption(); + void startServerEncryption(); + void transmit(); + bool startHandshake(); + void disconnectFromHost(); + void disconnected(); + QSslCipher sessionCipher() const; + + static QSslCipher QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher); + static QList<QSslCertificate> STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509); + Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); + static QString getErrorsFromOpenSsl(); +}; + +#if defined(Q_OS_SYMBIAN) + +#include <QByteArray> +#include <e32base.h> +#include <f32file.h> +#include <unifiedcertstore.h> // link against certstore.lib +#include <ccertattributefilter.h> // link against ctframework.lib + +// The purpose of this class is to wrap the asynchronous API of Symbian certificate store to one +// synchronizable call. The user of this class needs to provide a TRequestStatus object which can +// be used with User::WaitForRequest() unlike with the calls of the certificate store API. +// A thread is used instead of a CActiveSchedulerWait scheme, because that would make the call +// asynchronous (other events might be processed during the call even though the call would be seemingly +// synchronous). + +class CSymbianCertificateRetriever : public CActive +{ +public: + static CSymbianCertificateRetriever* NewL(); + ~CSymbianCertificateRetriever(); + + int GetCertificates(QList<QByteArray> &aCertificates); + +private: + void ConstructL(); + CSymbianCertificateRetriever(); + static TInt ThreadEntryPoint(TAny* aParams); + void doThreadEntryL(); + void GetCertificateL(); + void DoCancel(); + void RunL(); + TInt RunError(TInt aError); + +private: + enum { + Initializing, + Listing, + RetrievingCertificates + } iState; + + RThread iThread; + CUnifiedCertStore* iCertStore; + RMPointerArray<CCTCertInfo> iCertInfos; + CCertAttributeFilter* iCertFilter; + TInt iCurrentCertIndex; + QByteArray iCertificateData; + TPtr8 iCertificatePtr; + QList<QByteArray>* iCertificates; + TInt iSequenceError; +}; + + +#endif + + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp new file mode 100644 index 0000000000..b1310ccb8d --- /dev/null +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -0,0 +1,888 @@ +/**************************************************************************** +** +** 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 "qsslsocket_openssl_symbols_p.h" + +#ifdef Q_OS_WIN +# include <private/qsystemlibrary_p.h> +#else +# include <QtCore/qlibrary.h> +#endif +#include <QtCore/qmutex.h> +#include <private/qmutexpool_p.h> +#include <QtCore/qdatetime.h> +#if defined(Q_OS_UNIX) +#include <QtCore/qdir.h> +#endif + +QT_BEGIN_NAMESPACE + +/* + Note to maintainer: + ------------------- + + We load OpenSSL symbols dynamically. Because symbols are known to + disappear, and signatures sometimes change, between releases, we need to + be careful about how this is done. To ensure we don't end up dereferencing + null function pointers, and continue running even if certain functions are + missing, we define helper functions for each of the symbols we load from + OpenSSL, all prefixed with "q_" (declared in + qsslsocket_openssl_symbols_p.h). So instead of calling SSL_connect + directly, we call q_SSL_connect, which is a function that checks if the + actual SSL_connect fptr is null, and returns a failure if it is, or calls + SSL_connect if it isn't. + + This requires a somewhat tedious process of declaring each function we + want to call in OpenSSL thrice: once with the q_, in _p.h, once using the + DEFINEFUNC macros below, and once in the function that actually resolves + the symbols, below the DEFINEFUNC declarations below. + + There's one DEFINEFUNC macro declared for every number of arguments + exposed by OpenSSL (feel free to extend when needed). The easiest thing to + do is to find an existing entry that matches the arg count of the function + you want to import, and do the same. + + The first macro arg is the function return type. The second is the + verbatim name of the function/symbol. Then follows a list of N pairs of + argument types with a variable name, and just the variable name (char *a, + a, char *b, b, etc). Finally there's two arguments - a suitable return + statement for the error case (for an int function, return 0 or return -1 + is usually right). Then either just "return" or DUMMYARG, the latter being + for void functions. + + Note: Take into account that these macros and declarations are processed + at compile-time, and the result depends on the OpenSSL headers the + compiling host has installed, but the symbols are resolved at run-time, + possibly with a different version of OpenSSL. +*/ + +#ifdef SSLEAY_MACROS +DEFINEFUNC3(void *, ASN1_dup, i2d_of_void *a, a, d2i_of_void *b, b, char *c, c, return 0, return) +#endif +DEFINEFUNC(long, ASN1_INTEGER_get, ASN1_INTEGER *a, a, return 0, return) +DEFINEFUNC(unsigned char *, ASN1_STRING_data, ASN1_STRING *a, a, return 0, return) +DEFINEFUNC(int, ASN1_STRING_length, ASN1_STRING *a, a, return 0, return) +DEFINEFUNC4(long, BIO_ctrl, BIO *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(int, BIO_free, BIO *a, a, return 0, return) +DEFINEFUNC(BIO *, BIO_new, BIO_METHOD *a, a, return 0, return) +DEFINEFUNC2(BIO *, BIO_new_mem_buf, void *a, a, int b, b, return 0, return) +DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) +DEFINEFUNC(BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return 0, return) +DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) +DEFINEFUNC(int, CRYPTO_num_locks, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(void, CRYPTO_set_locking_callback, void (*a)(int, int, const char *, int), a, return, DUMMYARG) +DEFINEFUNC(void, CRYPTO_set_id_callback, unsigned long (*a)(), a, return, DUMMYARG) +DEFINEFUNC(void, CRYPTO_free, void *a, a, return, DUMMYARG) +DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER < 0x00908000L +DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, unsigned char **b, b, long c, c, return 0, return) +#else // 0.9.8 broke SC and BC by changing this signature. +DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, const unsigned char **b, b, long c, c, return 0, return) +#endif +DEFINEFUNC2(char *, ERR_error_string, unsigned long a, a, char *b, b, return 0, return) +DEFINEFUNC(unsigned long, ERR_get_error, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_des_ede3_cbc, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC3(int, EVP_PKEY_assign, EVP_PKEY *a, a, int b, b, char *c, c, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_RSA, EVP_PKEY *a, a, RSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DSA, EVP_PKEY *a, a, DSA *b, b, return -1, return) +DEFINEFUNC(void, EVP_PKEY_free, EVP_PKEY *a, a, return, DUMMYARG) +DEFINEFUNC(DSA *, EVP_PKEY_get1_DSA, EVP_PKEY *a, a, return 0, return) +DEFINEFUNC(RSA *, EVP_PKEY_get1_RSA, EVP_PKEY *a, a, return 0, return) +DEFINEFUNC(EVP_PKEY *, EVP_PKEY_new, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(int, EVP_PKEY_type, int a, a, return NID_undef, return) +DEFINEFUNC2(int, i2d_X509, X509 *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC(const char *, OBJ_nid2sn, int a, a, return 0, return) +DEFINEFUNC(int, OBJ_obj2nid, const ASN1_OBJECT *a, a, return NID_undef, return) +#ifdef SSLEAY_MACROS +DEFINEFUNC6(void *, PEM_ASN1_read_bio, d2i_of_void *a, a, const char *b, b, BIO *c, c, void **d, d, pem_password_cb *e, e, void *f, f, return 0, return) +DEFINEFUNC6(void *, PEM_ASN1_write_bio, d2i_of_void *a, a, const char *b, b, BIO *c, c, void **d, d, pem_password_cb *e, e, void *f, f, return 0, return) +#else +DEFINEFUNC4(DSA *, PEM_read_bio_DSAPrivateKey, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return 0, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_DSAPrivateKey, BIO *a, a, DSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_RSAPrivateKey, BIO *a, a, RSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +#endif +DEFINEFUNC4(DSA *, PEM_read_bio_DSA_PUBKEY, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return 0, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSA_PUBKEY, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return) +DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return) +DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) +DEFINEFUNC(int, sk_num, STACK *a, a, return -1, return) +DEFINEFUNC2(void, sk_pop_free, STACK *a, a, void (*b)(void*), b, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +DEFINEFUNC(void, sk_free, _STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(void *, sk_value, STACK *a, a, int b, b, return 0, return) +#else +DEFINEFUNC(void, sk_free, STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(char *, sk_value, STACK *a, a, int b, b, return 0, return) +#endif +DEFINEFUNC(int, SSL_accept, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_clear, SSL *a, a, return -1, return) +DEFINEFUNC3(char *, SSL_CIPHER_description, SSL_CIPHER *a, a, char *b, b, int c, c, return 0, return) +DEFINEFUNC(int, SSL_connect, SSL *a, a, return -1, return) +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +DEFINEFUNC(int, SSL_CTX_check_private_key, const SSL_CTX *a, a, return -1, return) +#else +DEFINEFUNC(int, SSL_CTX_check_private_key, SSL_CTX *a, a, return -1, return) +#endif +DEFINEFUNC4(long, SSL_CTX_ctrl, SSL_CTX *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(void, SSL_CTX_free, SSL_CTX *a, a, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +DEFINEFUNC(SSL_CTX *, SSL_CTX_new, const SSL_METHOD *a, a, return 0, return) +#else +DEFINEFUNC(SSL_CTX *, SSL_CTX_new, SSL_METHOD *a, a, return 0, return) +#endif +DEFINEFUNC2(int, SSL_CTX_set_cipher_list, SSL_CTX *a, a, const char *b, b, return -1, return) +DEFINEFUNC(int, SSL_CTX_set_default_verify_paths, SSL_CTX *a, a, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_verify, SSL_CTX *a, a, int b, b, int (*c)(int, X509_STORE_CTX *), c, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_set_verify_depth, SSL_CTX *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_certificate, SSL_CTX *a, a, X509 *b, b, return -1, return) +DEFINEFUNC3(int, SSL_CTX_use_certificate_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, SSL_CTX_use_PrivateKey, SSL_CTX *a, a, EVP_PKEY *b, b, return -1, return) +DEFINEFUNC2(int, SSL_CTX_use_RSAPrivateKey, SSL_CTX *a, a, RSA *b, b, return -1, return) +DEFINEFUNC3(int, SSL_CTX_use_PrivateKey_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) +DEFINEFUNC(void, SSL_free, SSL *a, a, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, const SSL *a, a, return 0, return) +#else +DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, SSL *a, a, return 0, return) +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +DEFINEFUNC(const SSL_CIPHER *, SSL_get_current_cipher, SSL *a, a, return 0, return) +#else +DEFINEFUNC(SSL_CIPHER *, SSL_get_current_cipher, SSL *a, a, return 0, return) +#endif +DEFINEFUNC2(int, SSL_get_error, SSL *a, a, int b, b, return -1, return) +DEFINEFUNC(STACK_OF(X509) *, SSL_get_peer_cert_chain, SSL *a, a, return 0, return) +DEFINEFUNC(X509 *, SSL_get_peer_certificate, SSL *a, a, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +DEFINEFUNC(long, SSL_get_verify_result, const SSL *a, a, return -1, return) +#else +DEFINEFUNC(long, SSL_get_verify_result, SSL *a, a, return -1, return) +#endif +DEFINEFUNC(int, SSL_library_init, void, DUMMYARG, return -1, return) +DEFINEFUNC(void, SSL_load_error_strings, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(SSL *, SSL_new, SSL_CTX *a, a, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +DEFINEFUNC4(long, SSL_ctrl, SSL *a, a, int cmd, cmd, long larg, larg, const void *parg, parg, return -1, return) +#endif +DEFINEFUNC3(int, SSL_read, SSL *a, a, void *b, b, int c, c, return -1, return) +DEFINEFUNC3(void, SSL_set_bio, SSL *a, a, BIO *b, b, BIO *c, c, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_accept_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_connect_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(int, SSL_shutdown, SSL *a, a, return -1, return) +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +DEFINEFUNC(const SSL_METHOD *, SSLv2_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, SSLv3_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, SSLv23_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, TLSv1_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, SSLv2_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, SSLv3_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, SSLv23_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(const SSL_METHOD *, TLSv1_server_method, DUMMYARG, DUMMYARG, return 0, return) +#else +DEFINEFUNC(SSL_METHOD *, SSLv2_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, SSLv3_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, SSLv23_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, TLSv1_client_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, SSLv2_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, SSLv3_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, SSLv23_server_method, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(SSL_METHOD *, TLSv1_server_method, DUMMYARG, DUMMYARG, return 0, return) +#endif +DEFINEFUNC3(int, SSL_write, SSL *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, X509_cmp, X509 *a, a, X509 *b, b, return -1, return) +#ifndef SSLEAY_MACROS +DEFINEFUNC(X509 *, X509_dup, X509 *a, a, return 0, return) +#endif +DEFINEFUNC(ASN1_OBJECT *, X509_EXTENSION_get_object, X509_EXTENSION *a, a, return 0, return) +DEFINEFUNC(void, X509_free, X509 *a, a, return, DUMMYARG) +DEFINEFUNC2(X509_EXTENSION *, X509_get_ext, X509 *a, a, int b, b, return 0, return) +DEFINEFUNC(int, X509_get_ext_count, X509 *a, a, return 0, return) +DEFINEFUNC4(void *, X509_get_ext_d2i, X509 *a, a, int b, b, int *c, c, int *d, d, return 0, return) +DEFINEFUNC(X509_NAME *, X509_get_issuer_name, X509 *a, a, return 0, return) +DEFINEFUNC(X509_NAME *, X509_get_subject_name, X509 *a, a, return 0, return) +DEFINEFUNC(int, X509_verify_cert, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC3(char *, X509_NAME_oneline, X509_NAME *a, a, char *b, b, int c, c, return 0, return) +DEFINEFUNC(EVP_PKEY *, X509_PUBKEY_get, X509_PUBKEY *a, a, return 0, return) +DEFINEFUNC(void, X509_STORE_free, X509_STORE *a, a, return, DUMMYARG) +DEFINEFUNC(X509_STORE *, X509_STORE_new, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC2(int, X509_STORE_add_cert, X509_STORE *a, a, X509 *b, b, return 0, return) +DEFINEFUNC(void, X509_STORE_CTX_free, X509_STORE_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, X509_STORE_CTX_init, X509_STORE_CTX *a, a, X509_STORE *b, b, X509 *c, c, STACK_OF(X509) *d, d, return -1, return) +DEFINEFUNC2(int, X509_STORE_CTX_set_purpose, X509_STORE_CTX *a, a, int b, b, return -1, return) +DEFINEFUNC(X509_STORE_CTX *, X509_STORE_CTX_new, DUMMYARG, DUMMYARG, return 0, return) +#ifdef SSLEAY_MACROS +DEFINEFUNC2(int, i2d_DSAPrivateKey, const DSA *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC2(int, i2d_RSAPrivateKey, const RSA *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC3(RSA *, d2i_RSAPrivateKey, RSA **a, a, unsigned char **b, b, long c, c, return 0, return) +DEFINEFUNC3(DSA *, d2i_DSAPrivateKey, DSA **a, a, unsigned char **b, b, long c, c, return 0, return) +#endif +DEFINEFUNC(void, OPENSSL_add_all_algorithms_noconf, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(void, OPENSSL_add_all_algorithms_conf, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return) +DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return) + +#ifdef Q_OS_SYMBIAN +#define RESOLVEFUNC(func, ordinal, lib) \ + if (!(_q_##func = _q_PTR_##func(lib->resolve(#ordinal)))) \ + qWarning("QSslSocket: cannot resolve "#func); +#else +#define RESOLVEFUNC(func) \ + if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \ + && !(_q_##func = _q_PTR_##func(libs.second->resolve(#func)))) \ + qWarning("QSslSocket: cannot resolve "#func); +#endif + +#if !defined QT_LINKED_OPENSSL + +#ifdef QT_NO_LIBRARY +bool q_resolveOpenSslSymbols() +{ + qWarning("QSslSocket: unable to resolve symbols. " + "QT_NO_LIBRARY is defined which means runtime resolving of " + "libraries won't work."); + qWarning("Either compile Qt statically or with support for runtime resolving " + "of libraries."); + return false; +} +#else + +# ifdef Q_OS_UNIX +static bool libGreaterThan(const QString &lhs, const QString &rhs) +{ + QStringList lhsparts = lhs.split(QLatin1Char('.')); + QStringList rhsparts = rhs.split(QLatin1Char('.')); + Q_ASSERT(lhsparts.count() > 1 && rhsparts.count() > 1); + + for (int i = 1; i < rhsparts.count(); ++i) { + if (lhsparts.count() <= i) + // left hand side is shorter, so it's less than rhs + return false; + + bool ok = false; + int b = 0; + int a = lhsparts.at(i).toInt(&ok); + if (ok) + b = rhsparts.at(i).toInt(&ok); + if (ok) { + // both toInt succeeded + if (a == b) + continue; + return a > b; + } else { + // compare as strings; + if (lhsparts.at(i) == rhsparts.at(i)) + continue; + return lhsparts.at(i) > rhsparts.at(i); + } + } + + // they compared strictly equally so far + // lhs cannot be less than rhs + return true; +} + +static QStringList findAllLibSsl() +{ + QStringList paths; +# ifdef Q_OS_DARWIN + paths = QString::fromLatin1(qgetenv("DYLD_LIBRARY_PATH")) + .split(QLatin1Char(':'), QString::SkipEmptyParts); +# else + paths = QString::fromLatin1(qgetenv("LD_LIBRARY_PATH")) + .split(QLatin1Char(':'), QString::SkipEmptyParts); +# endif + paths << QLatin1String("/lib") << QLatin1String("/usr/lib") << QLatin1String("/usr/local/lib"); + + QStringList foundSsls; + foreach (const QString &path, paths) { + QDir dir = QDir(path); + QStringList entryList = dir.entryList(QStringList() << QLatin1String("libssl.*"), QDir::Files); + + qSort(entryList.begin(), entryList.end(), libGreaterThan); + foreach (const QString &entry, entryList) + foundSsls << path + QLatin1Char('/') + entry; + } + + return foundSsls; +} +# endif + +#ifdef Q_OS_WIN +static QPair<QSystemLibrary*, QSystemLibrary*> loadOpenSslWin32() +{ + QPair<QSystemLibrary*,QSystemLibrary*> pair; + pair.first = 0; + pair.second = 0; + + QSystemLibrary *ssleay32 = new QSystemLibrary(QLatin1String("ssleay32")); + if (!ssleay32->load(false)) { + // Cannot find ssleay32.dll + delete ssleay32; + return pair; + } + + QSystemLibrary *libeay32 = new QSystemLibrary(QLatin1String("libeay32")); + if (!libeay32->load(false)) { + delete ssleay32; + delete libeay32; + return pair; + } + + pair.first = ssleay32; + pair.second = libeay32; + return pair; +} +#else + +static QPair<QLibrary*, QLibrary*> loadOpenSsl() +{ + QPair<QLibrary*,QLibrary*> pair; + pair.first = 0; + pair.second = 0; + +# if defined(Q_OS_SYMBIAN) + QLibrary *libssl = new QLibrary(QLatin1String("libssl")); + if (!libssl->load()) { + // Cannot find ssleay32.dll + delete libssl; + return pair; + } + + QLibrary *libcrypto = new QLibrary(QLatin1String("libcrypto")); + if (!libcrypto->load()) { + delete libcrypto; + delete libssl; + return pair; + } + + pair.first = libssl; + pair.second = libcrypto; + return pair; +# elif defined(Q_OS_UNIX) + QLibrary *&libssl = pair.first; + QLibrary *&libcrypto = pair.second; + libssl = new QLibrary; + libcrypto = new QLibrary; + + // Try to find the libssl library on the system. + // + // Up until Qt 4.3, this only searched for the "ssl" library at version -1, that + // is, libssl.so on most Unix systems. However, the .so file isn't present in + // user installations because it's considered a development file. + // + // The right thing to do is to load the library at the major version we know how + // to work with: the SHLIB_VERSION_NUMBER version (macro defined in opensslv.h) + // + // However, OpenSSL is a well-known case of binary-compatibility breakage. To + // avoid such problems, many system integrators and Linux distributions change + // the soname of the binary, letting the full version number be the soname. So + // we'll find libssl.so.0.9.7, libssl.so.0.9.8, etc. in the system. For that + // reason, we will search a few common paths (see findAllLibSsl() above) in hopes + // we find one that works. + // + // It is important, however, to try the canonical name and the unversioned name + // without going through the loop. By not specifying a path, we let the system + // dlopen(3) function determine it for us. This will include any DT_RUNPATH or + // DT_RPATH tags on our library header as well as other system-specific search + // paths. See the man page for dlopen(3) on your system for more information. + +#ifdef Q_OS_OPENBSD + libcrypto->setLoadHints(QLibrary::ExportExternalSymbolsHint); +#endif +#ifdef SHLIB_VERSION_NUMBER + // first attempt: the canonical name is libssl.so.<SHLIB_VERSION_NUMBER> + libssl->setFileNameAndVersion(QLatin1String("ssl"), QLatin1String(SHLIB_VERSION_NUMBER)); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), QLatin1String(SHLIB_VERSION_NUMBER)); + if (libcrypto->load() && libssl->load()) { + // libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found + return pair; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif + + // second attempt: find the development files libssl.so and libcrypto.so + libssl->setFileNameAndVersion(QLatin1String("ssl"), -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), -1); + if (libcrypto->load() && libssl->load()) { + // libssl.so.0 and libcrypto.so.0 found + return pair; + } else { + libssl->unload(); + libcrypto->unload(); + } + + // third attempt: loop on the most common library paths and find libssl + QStringList sslList = findAllLibSsl(); + foreach (const QString &ssl, sslList) { + QString crypto = ssl; + crypto.replace(QLatin1String("ssl"), QLatin1String("crypto")); + libssl->setFileNameAndVersion(ssl, -1); + libcrypto->setFileNameAndVersion(crypto, -1); + if (libcrypto->load() && libssl->load()) { + // libssl.so.0 and libcrypto.so.0 found + return pair; + } else { + libssl->unload(); + libcrypto->unload(); + } + } + + // failed to load anything + delete libssl; + delete libcrypto; + libssl = libcrypto = 0; + return pair; + +# else + // not implemented for this platform yet + return pair; +# endif +} +#endif + +bool q_resolveOpenSslSymbols() +{ + static volatile bool symbolsResolved = false; + static volatile bool triedToResolveSymbols = false; +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet((void *)&q_SSL_library_init)); +#endif + if (symbolsResolved) + return true; + if (triedToResolveSymbols) + return false; + triedToResolveSymbols = true; + +#ifdef Q_OS_WIN + QPair<QSystemLibrary *, QSystemLibrary *> libs = loadOpenSslWin32(); +#else + QPair<QLibrary *, QLibrary *> libs = loadOpenSsl(); +#endif + if (!libs.first || !libs.second) + // failed to load them + return false; + +#ifdef Q_OS_SYMBIAN +#ifdef SSLEAY_MACROS + RESOLVEFUNC(ASN1_dup, 125, libs.second ) +#endif + RESOLVEFUNC(ASN1_INTEGER_get, 48, libs.second ) + RESOLVEFUNC(ASN1_STRING_data, 71, libs.second ) + RESOLVEFUNC(ASN1_STRING_length, 76, libs.second ) + RESOLVEFUNC(BIO_ctrl, 184, libs.second ) + RESOLVEFUNC(BIO_free, 209, libs.second ) + RESOLVEFUNC(BIO_new, 222, libs.second ) + RESOLVEFUNC(BIO_new_mem_buf, 230, libs.second ) + RESOLVEFUNC(BIO_read, 244, libs.second ) + RESOLVEFUNC(BIO_s_mem, 251, libs.second ) + RESOLVEFUNC(BIO_write, 269, libs.second ) + RESOLVEFUNC(BN_num_bits, 387, libs.second ) + RESOLVEFUNC(CRYPTO_free, 469, libs.second ) + RESOLVEFUNC(CRYPTO_num_locks, 500, libs.second ) + RESOLVEFUNC(CRYPTO_set_id_callback, 513, libs.second ) + RESOLVEFUNC(CRYPTO_set_locking_callback, 516, libs.second ) + RESOLVEFUNC(DSA_free, 594, libs.second ) + RESOLVEFUNC(ERR_error_string, 744, libs.second ) + RESOLVEFUNC(ERR_get_error, 749, libs.second ) + RESOLVEFUNC(EVP_des_ede3_cbc, 919, libs.second ) + RESOLVEFUNC(EVP_PKEY_assign, 859, libs.second ) + RESOLVEFUNC(EVP_PKEY_set1_RSA, 880, libs.second ) + RESOLVEFUNC(EVP_PKEY_set1_DSA, 879, libs.second ) + RESOLVEFUNC(EVP_PKEY_free, 867, libs.second ) + RESOLVEFUNC(EVP_PKEY_get1_DSA, 869, libs.second ) + RESOLVEFUNC(EVP_PKEY_get1_RSA, 870, libs.second ) + RESOLVEFUNC(EVP_PKEY_new, 876, libs.second ) + RESOLVEFUNC(EVP_PKEY_type, 882, libs.second ) + RESOLVEFUNC(OBJ_nid2sn, 1036, libs.second ) + RESOLVEFUNC(OBJ_obj2nid, 1037, libs.second ) +#ifdef SSLEAY_MACROS // ### verify + RESOLVEFUNC(PEM_ASN1_read_bio, 1180, libs.second ) +#else + RESOLVEFUNC(PEM_read_bio_DSAPrivateKey, 1219, libs.second ) + RESOLVEFUNC(PEM_read_bio_RSAPrivateKey, 1228, libs.second ) + RESOLVEFUNC(PEM_write_bio_DSAPrivateKey, 1260, libs.second ) + RESOLVEFUNC(PEM_write_bio_RSAPrivateKey, 1271, libs.second ) +#endif + RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY, 1220, libs.second ) + RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY, 1230, libs.second ) + RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY, 1261, libs.second ) + RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY, 1273, libs.second ) + RESOLVEFUNC(RAND_seed, 1426, libs.second ) + RESOLVEFUNC(RAND_status, 1429, libs.second ) + RESOLVEFUNC(RSA_free, 1450, libs.second ) + RESOLVEFUNC(sk_free, 2571, libs.second ) + RESOLVEFUNC(sk_num, 2576, libs.second ) + RESOLVEFUNC(sk_pop_free, 2578, libs.second ) + RESOLVEFUNC(sk_value, 2585, libs.second ) + RESOLVEFUNC(SSL_CIPHER_description, 11, libs.first ) + RESOLVEFUNC(SSL_CTX_check_private_key, 21, libs.first ) + RESOLVEFUNC(SSL_CTX_ctrl, 22, libs.first ) + RESOLVEFUNC(SSL_CTX_free, 24, libs.first ) + RESOLVEFUNC(SSL_CTX_new, 35, libs.first ) + RESOLVEFUNC(SSL_CTX_set_cipher_list, 40, libs.first ) + RESOLVEFUNC(SSL_CTX_set_default_verify_paths, 44, libs.first ) + RESOLVEFUNC(SSL_CTX_set_verify, 56, libs.first ) + RESOLVEFUNC(SSL_CTX_set_verify_depth, 57, libs.first ) + RESOLVEFUNC(SSL_CTX_use_certificate, 64, libs.first ) + RESOLVEFUNC(SSL_CTX_use_certificate_file, 67, libs.first ) + RESOLVEFUNC(SSL_CTX_use_PrivateKey, 58, libs.first ) + RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey, 61, libs.first ) + RESOLVEFUNC(SSL_CTX_use_PrivateKey_file, 60, libs.first ) + RESOLVEFUNC(SSL_accept, 82, libs.first ) + RESOLVEFUNC(SSL_clear, 92, libs.first ) + RESOLVEFUNC(SSL_connect, 93, libs.first ) + RESOLVEFUNC(SSL_free, 99, libs.first ) + RESOLVEFUNC(SSL_get_ciphers, 104, libs.first ) + RESOLVEFUNC(SSL_get_current_cipher, 106, libs.first ) + RESOLVEFUNC(SSL_get_error, 110, libs.first ) + RESOLVEFUNC(SSL_get_peer_cert_chain, 117, libs.first ) + RESOLVEFUNC(SSL_get_peer_certificate, 118, libs.first ) + RESOLVEFUNC(SSL_get_verify_result, 132, libs.first ) + RESOLVEFUNC(SSL_library_init, 137, libs.first ) + RESOLVEFUNC(SSL_load_error_strings, 139, libs.first ) + RESOLVEFUNC(SSL_new, 140, libs.first ) +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) + RESOLVEFUNC(SSL_ctrl, 95, libs.first ) +#endif + RESOLVEFUNC(SSL_read, 143, libs.first ) + RESOLVEFUNC(SSL_set_accept_state, 148, libs.first ) + RESOLVEFUNC(SSL_set_bio, 149, libs.first ) + RESOLVEFUNC(SSL_set_connect_state, 152, libs.first ) + RESOLVEFUNC(SSL_shutdown, 173, libs.first ) + RESOLVEFUNC(SSL_write, 188, libs.first ) + RESOLVEFUNC(SSLv2_client_method, 192, libs.first ) + RESOLVEFUNC(SSLv3_client_method, 195, libs.first ) + RESOLVEFUNC(SSLv23_client_method, 189, libs.first ) + RESOLVEFUNC(TLSv1_client_method, 198, libs.first ) + RESOLVEFUNC(SSLv2_server_method, 194, libs.first ) + RESOLVEFUNC(SSLv3_server_method, 197, libs.first ) + RESOLVEFUNC(SSLv23_server_method, 191, libs.first ) + RESOLVEFUNC(TLSv1_server_method, 200, libs.first ) + RESOLVEFUNC(SSL_CTX_load_verify_locations, 34, libs.first ) + RESOLVEFUNC(X509_NAME_oneline, 1830, libs.second ) + RESOLVEFUNC(X509_PUBKEY_get, 1844, libs.second ) + RESOLVEFUNC(X509_STORE_free, 1939, libs.second ) + RESOLVEFUNC(X509_STORE_new, 1942, libs.second ) + RESOLVEFUNC(X509_STORE_add_cert, 1936, libs.second ) + RESOLVEFUNC(X509_STORE_CTX_free, 1907, libs.second ) + RESOLVEFUNC(X509_STORE_CTX_init, 1919, libs.second ) + RESOLVEFUNC(X509_STORE_CTX_new, 1920, libs.second ) + RESOLVEFUNC(X509_STORE_CTX_set_purpose, 1931, libs.second ) + RESOLVEFUNC(X509_cmp, 1992, libs.second ) +#ifndef SSLEAY_MACROS + RESOLVEFUNC(X509_dup, 1997, libs.second ) +#endif + RESOLVEFUNC(X509_EXTENSION_get_object, 1785, libs.second ) + RESOLVEFUNC(X509_free, 2001, libs.second ) + RESOLVEFUNC(X509_get_ext, 2012, libs.second ) + RESOLVEFUNC(X509_get_ext_count, 2016, libs.second ) + RESOLVEFUNC(X509_get_ext_d2i, 2017, libs.second ) + RESOLVEFUNC(X509_get_issuer_name, 2018, libs.second ) + RESOLVEFUNC(X509_get_subject_name, 2022, libs.second ) + RESOLVEFUNC(X509_verify_cert, 2069, libs.second ) + RESOLVEFUNC(d2i_X509, 2309, libs.second ) + RESOLVEFUNC(i2d_X509, 2489, libs.second ) +#ifdef SSLEAY_MACROS + RESOLVEFUNC(i2d_DSAPrivateKey, 2395, libs.second ) + RESOLVEFUNC(i2d_RSAPrivateKey, 2476, libs.second ) + RESOLVEFUNC(d2i_DSAPrivateKey, 2220, libs.second ) + RESOLVEFUNC(d2i_RSAPrivateKey, 2296, libs.second ) +#endif + RESOLVEFUNC(OPENSSL_add_all_algorithms_noconf, 1153, libs.second ) + RESOLVEFUNC(OPENSSL_add_all_algorithms_conf, 1152, libs.second ) + RESOLVEFUNC(SSLeay, 1504, libs.second ) +#else // Q_OS_SYMBIAN +#ifdef SSLEAY_MACROS + RESOLVEFUNC(ASN1_dup) +#endif + RESOLVEFUNC(ASN1_INTEGER_get) + RESOLVEFUNC(ASN1_STRING_data) + RESOLVEFUNC(ASN1_STRING_length) + RESOLVEFUNC(BIO_ctrl) + RESOLVEFUNC(BIO_free) + RESOLVEFUNC(BIO_new) + RESOLVEFUNC(BIO_new_mem_buf) + RESOLVEFUNC(BIO_read) + RESOLVEFUNC(BIO_s_mem) + RESOLVEFUNC(BIO_write) + RESOLVEFUNC(BN_num_bits) + RESOLVEFUNC(CRYPTO_free) + RESOLVEFUNC(CRYPTO_num_locks) + RESOLVEFUNC(CRYPTO_set_id_callback) + RESOLVEFUNC(CRYPTO_set_locking_callback) + RESOLVEFUNC(DSA_free) + RESOLVEFUNC(ERR_error_string) + RESOLVEFUNC(ERR_get_error) + RESOLVEFUNC(EVP_des_ede3_cbc) + RESOLVEFUNC(EVP_PKEY_assign) + RESOLVEFUNC(EVP_PKEY_set1_RSA) + RESOLVEFUNC(EVP_PKEY_set1_DSA) + RESOLVEFUNC(EVP_PKEY_free) + RESOLVEFUNC(EVP_PKEY_get1_DSA) + RESOLVEFUNC(EVP_PKEY_get1_RSA) + RESOLVEFUNC(EVP_PKEY_new) + RESOLVEFUNC(EVP_PKEY_type) + RESOLVEFUNC(OBJ_nid2sn) + RESOLVEFUNC(OBJ_obj2nid) +#ifdef SSLEAY_MACROS // ### verify + RESOLVEFUNC(PEM_ASN1_read_bio) +#else + RESOLVEFUNC(PEM_read_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_read_bio_RSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) +#endif + RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY) + RESOLVEFUNC(RAND_seed) + RESOLVEFUNC(RAND_status) + RESOLVEFUNC(RSA_free) + RESOLVEFUNC(sk_free) + RESOLVEFUNC(sk_num) + RESOLVEFUNC(sk_pop_free) + RESOLVEFUNC(sk_value) + RESOLVEFUNC(SSL_CIPHER_description) + RESOLVEFUNC(SSL_CTX_check_private_key) + RESOLVEFUNC(SSL_CTX_ctrl) + RESOLVEFUNC(SSL_CTX_free) + RESOLVEFUNC(SSL_CTX_new) + RESOLVEFUNC(SSL_CTX_set_cipher_list) + RESOLVEFUNC(SSL_CTX_set_default_verify_paths) + RESOLVEFUNC(SSL_CTX_set_verify) + RESOLVEFUNC(SSL_CTX_set_verify_depth) + RESOLVEFUNC(SSL_CTX_use_certificate) + RESOLVEFUNC(SSL_CTX_use_certificate_file) + RESOLVEFUNC(SSL_CTX_use_PrivateKey) + RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey) + RESOLVEFUNC(SSL_CTX_use_PrivateKey_file) + RESOLVEFUNC(SSL_accept) + RESOLVEFUNC(SSL_clear) + RESOLVEFUNC(SSL_connect) + RESOLVEFUNC(SSL_free) + RESOLVEFUNC(SSL_get_ciphers) + RESOLVEFUNC(SSL_get_current_cipher) + RESOLVEFUNC(SSL_get_error) + RESOLVEFUNC(SSL_get_peer_cert_chain) + RESOLVEFUNC(SSL_get_peer_certificate) + RESOLVEFUNC(SSL_get_verify_result) + RESOLVEFUNC(SSL_library_init) + RESOLVEFUNC(SSL_load_error_strings) + RESOLVEFUNC(SSL_new) +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) + RESOLVEFUNC(SSL_ctrl) +#endif + RESOLVEFUNC(SSL_read) + RESOLVEFUNC(SSL_set_accept_state) + RESOLVEFUNC(SSL_set_bio) + RESOLVEFUNC(SSL_set_connect_state) + RESOLVEFUNC(SSL_shutdown) + RESOLVEFUNC(SSL_write) + RESOLVEFUNC(SSLv2_client_method) + RESOLVEFUNC(SSLv3_client_method) + RESOLVEFUNC(SSLv23_client_method) + RESOLVEFUNC(TLSv1_client_method) + RESOLVEFUNC(SSLv2_server_method) + RESOLVEFUNC(SSLv3_server_method) + RESOLVEFUNC(SSLv23_server_method) + RESOLVEFUNC(TLSv1_server_method) + RESOLVEFUNC(X509_NAME_oneline) + RESOLVEFUNC(X509_PUBKEY_get) + RESOLVEFUNC(X509_STORE_free) + RESOLVEFUNC(X509_STORE_new) + RESOLVEFUNC(X509_STORE_add_cert) + RESOLVEFUNC(X509_STORE_CTX_free) + RESOLVEFUNC(X509_STORE_CTX_init) + RESOLVEFUNC(X509_STORE_CTX_new) + RESOLVEFUNC(X509_STORE_CTX_set_purpose) + RESOLVEFUNC(X509_cmp) +#ifndef SSLEAY_MACROS + RESOLVEFUNC(X509_dup) +#endif + RESOLVEFUNC(X509_EXTENSION_get_object) + RESOLVEFUNC(X509_free) + RESOLVEFUNC(X509_get_ext) + RESOLVEFUNC(X509_get_ext_count) + RESOLVEFUNC(X509_get_ext_d2i) + RESOLVEFUNC(X509_get_issuer_name) + RESOLVEFUNC(X509_get_subject_name) + RESOLVEFUNC(X509_verify_cert) + RESOLVEFUNC(d2i_X509) + RESOLVEFUNC(i2d_X509) +#ifdef SSLEAY_MACROS + RESOLVEFUNC(i2d_DSAPrivateKey) + RESOLVEFUNC(i2d_RSAPrivateKey) + RESOLVEFUNC(d2i_DSAPrivateKey) + RESOLVEFUNC(d2i_RSAPrivateKey) +#endif + RESOLVEFUNC(OPENSSL_add_all_algorithms_noconf) + RESOLVEFUNC(OPENSSL_add_all_algorithms_conf) + RESOLVEFUNC(SSL_CTX_load_verify_locations) + RESOLVEFUNC(SSLeay) +#endif // Q_OS_SYMBIAN + symbolsResolved = true; + delete libs.first; + delete libs.second; + return true; +} +#endif // QT_NO_LIBRARY + +#else // !defined QT_LINKED_OPENSSL + +bool q_resolveOpenSslSymbols() +{ +#ifdef QT_NO_OPENSSL + return false; +#endif + return true; +} +#endif // !defined QT_LINKED_OPENSSL + +//============================================================================== +// contributed by Jay Case of Sarvega, Inc.; http://sarvega.com/ +// Based on X509_cmp_time() for intitial buffer hacking. +//============================================================================== +QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime) +{ + size_t lTimeLength = aTime->length; + char *pString = (char *) aTime->data; + + if (aTime->type == V_ASN1_UTCTIME) { + + char lBuffer[24]; + char *pBuffer = lBuffer; + + if ((lTimeLength < 11) || (lTimeLength > 17)) + return QDateTime(); + + memcpy(pBuffer, pString, 10); + pBuffer += 10; + pString += 10; + + if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) { + *pBuffer++ = '0'; + *pBuffer++ = '0'; + } else { + *pBuffer++ = *pString++; + *pBuffer++ = *pString++; + // Skip any fractional seconds... + if (*pString == '.') { + pString++; + while ((*pString >= '0') && (*pString <= '9')) + pString++; + } + } + + *pBuffer++ = 'Z'; + *pBuffer++ = '\0'; + + time_t lSecondsFromUCT; + if (*pString == 'Z') { + lSecondsFromUCT = 0; + } else { + if ((*pString != '+') && (*pString != '-')) + return QDateTime(); + + lSecondsFromUCT = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60; + lSecondsFromUCT += (pString[3] - '0') * 10 + (pString[4] - '0'); + lSecondsFromUCT *= 60; + if (*pString == '-') + lSecondsFromUCT = -lSecondsFromUCT; + } + + tm lTime; + lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0'); + lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); + lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); + lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0'); + lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1; + lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0'); + if (lTime.tm_year < 50) + lTime.tm_year += 100; // RFC 2459 + + QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + + QDateTime result(resDate, resTime, Qt::UTC); + result = result.addSecs(lSecondsFromUCT); + return result; + + } else if (aTime->type == V_ASN1_GENERALIZEDTIME) { + + if (lTimeLength < 15) + return QDateTime(); // hopefully never triggered + + // generalized time is always YYYYMMDDHHMMSSZ (RFC 2459, section 4.1.2.5.2) + tm lTime; + lTime.tm_sec = ((pString[12] - '0') * 10) + (pString[13] - '0'); + lTime.tm_min = ((pString[10] - '0') * 10) + (pString[11] - '0'); + lTime.tm_hour = ((pString[8] - '0') * 10) + (pString[9] - '0'); + lTime.tm_mday = ((pString[6] - '0') * 10) + (pString[7] - '0'); + lTime.tm_mon = (((pString[4] - '0') * 10) + (pString[5] - '0')); + lTime.tm_year = ((pString[0] - '0') * 1000) + ((pString[1] - '0') * 100) + + ((pString[2] - '0') * 10) + (pString[3] - '0'); + + QDate resDate(lTime.tm_year, lTime.tm_mon, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + + QDateTime result(resDate, resTime, Qt::UTC); + return result; + + } else { + qWarning("unsupported date format detected"); + return QDateTime(); + } + +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h new file mode 100644 index 0000000000..49830acc1e --- /dev/null +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_OPENSSL_SYMBOLS_P_H +#define QSSLSOCKET_OPENSSL_SYMBOLS_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 QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qsslsocket_openssl_p.h" + +QT_BEGIN_NAMESPACE + +#define DUMMYARG + +#if !defined QT_LINKED_OPENSSL +// **************** Shared declarations ****************** +// ret func(arg) + +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a); \ + } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func);\ + err; \ + } \ + funcret _q_##func(a, b); \ + } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c); \ + } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { \ + if (!_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { \ + if (_q_##func) { \ + qWarning("QSslSocket: cannot call unresolved function "#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g, h, i); \ + } +// **************** Shared declarations ****************** + +#else // !defined QT_LINKED_OPENSSL + +// **************** Static declarations ****************** + +// ret func(arg) +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + ret q_##func(arg) { funcret func(a); } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + ret q_##func(arg1, arg2) { funcret func(a, b); } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + ret q_##func(arg1, arg2, arg3) { funcret func(a, b, c); } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4) { funcret func(a, b, c, d); } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { funcret func(a, b, c, d, e); } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { funcret func(a, b, c, d, e, f); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { funcret func(a, b, c, d, e, f, g); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { funcret func(a, b, c, d, e, f, g, h, i); } + +// **************** Static declarations ****************** + +#endif // !defined QT_LINKED_OPENSSL + +bool q_resolveOpenSslSymbols(); +long q_ASN1_INTEGER_get(ASN1_INTEGER *a); +unsigned char * q_ASN1_STRING_data(ASN1_STRING *a); +int q_ASN1_STRING_length(ASN1_STRING *a); +long q_BIO_ctrl(BIO *a, int b, long c, void *d); +int q_BIO_free(BIO *a); +BIO *q_BIO_new(BIO_METHOD *a); +BIO *q_BIO_new_mem_buf(void *a, int b); +int q_BIO_read(BIO *a, void *b, int c); +BIO_METHOD *q_BIO_s_mem(); +int q_BIO_write(BIO *a, const void *b, int c); +int q_BN_num_bits(const BIGNUM *a); +int q_CRYPTO_num_locks(); +void q_CRYPTO_set_locking_callback(void (*a)(int, int, const char *, int)); +void q_CRYPTO_set_id_callback(unsigned long (*a)()); +void q_CRYPTO_free(void *a); +void q_DSA_free(DSA *a); +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +X509 *q_d2i_X509(X509 **a, const unsigned char **b, long c); +#else +X509 *q_d2i_X509(X509 **a, unsigned char **b, long c); +#endif +char *q_ERR_error_string(unsigned long a, char *b); +unsigned long q_ERR_get_error(); +const EVP_CIPHER *q_EVP_des_ede3_cbc(); +int q_EVP_PKEY_assign(EVP_PKEY *a, int b, char *c); +int q_EVP_PKEY_set1_RSA(EVP_PKEY *a, RSA *b); +int q_EVP_PKEY_set1_DSA(EVP_PKEY *a, DSA *b); +void q_EVP_PKEY_free(EVP_PKEY *a); +RSA *q_EVP_PKEY_get1_RSA(EVP_PKEY *a); +DSA *q_EVP_PKEY_get1_DSA(EVP_PKEY *a); +int q_EVP_PKEY_type(int a); +EVP_PKEY *q_EVP_PKEY_new(); +int q_i2d_X509(X509 *a, unsigned char **b); +const char *q_OBJ_nid2sn(int a); +int q_OBJ_obj2nid(const ASN1_OBJECT *a); +#ifdef SSLEAY_MACROS +// ### verify +void *q_PEM_ASN1_read_bio(d2i_of_void *a, const char *b, BIO *c, void **d, pem_password_cb *e, + void *f); +// ### ditto for write +#else +DSA *q_PEM_read_bio_DSAPrivateKey(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +#endif +DSA *q_PEM_read_bio_DSA_PUBKEY(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSA_PUBKEY(BIO *a, RSA **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b); +int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b); +void q_RAND_seed(const void *a, int b); +int q_RAND_status(); +void q_RSA_free(RSA *a); +int q_sk_num(STACK *a); +void q_sk_pop_free(STACK *a, void (*b)(void *)); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +void q_sk_free(_STACK *a); +void * q_sk_value(STACK *a, int b); +#else +void q_sk_free(STACK *a); +char * q_sk_value(STACK *a, int b); +#endif +int q_SSL_accept(SSL *a); +int q_SSL_clear(SSL *a); +char *q_SSL_CIPHER_description(SSL_CIPHER *a, char *b, int c); +int q_SSL_connect(SSL *a); +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +int q_SSL_CTX_check_private_key(const SSL_CTX *a); +#else +int q_SSL_CTX_check_private_key(SSL_CTX *a); +#endif +long q_SSL_CTX_ctrl(SSL_CTX *a, int b, long c, void *d); +void q_SSL_CTX_free(SSL_CTX *a); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +SSL_CTX *q_SSL_CTX_new(const SSL_METHOD *a); +#else +SSL_CTX *q_SSL_CTX_new(SSL_METHOD *a); +#endif +int q_SSL_CTX_set_cipher_list(SSL_CTX *a, const char *b); +int q_SSL_CTX_set_default_verify_paths(SSL_CTX *a); +void q_SSL_CTX_set_verify(SSL_CTX *a, int b, int (*c)(int, X509_STORE_CTX *)); +void q_SSL_CTX_set_verify_depth(SSL_CTX *a, int b); +int q_SSL_CTX_use_certificate(SSL_CTX *a, X509 *b); +int q_SSL_CTX_use_certificate_file(SSL_CTX *a, const char *b, int c); +int q_SSL_CTX_use_PrivateKey(SSL_CTX *a, EVP_PKEY *b); +int q_SSL_CTX_use_RSAPrivateKey(SSL_CTX *a, RSA *b); +int q_SSL_CTX_use_PrivateKey_file(SSL_CTX *a, const char *b, int c); +void q_SSL_free(SSL *a); +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(const SSL *a); +#else +STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(SSL *a); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +const SSL_CIPHER *q_SSL_get_current_cipher(SSL *a); +#else +SSL_CIPHER *q_SSL_get_current_cipher(SSL *a); +#endif +int q_SSL_get_error(SSL *a, int b); +STACK_OF(X509) *q_SSL_get_peer_cert_chain(SSL *a); +X509 *q_SSL_get_peer_certificate(SSL *a); +#if OPENSSL_VERSION_NUMBER >= 0x00908000L +// 0.9.8 broke SC and BC by changing this function's signature. +long q_SSL_get_verify_result(const SSL *a); +#else +long q_SSL_get_verify_result(SSL *a); +#endif +int q_SSL_library_init(); +void q_SSL_load_error_strings(); +SSL *q_SSL_new(SSL_CTX *a); +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +long q_SSL_ctrl(SSL *ssl,int cmd, long larg, const void *parg); +#endif +int q_SSL_read(SSL *a, void *b, int c); +void q_SSL_set_bio(SSL *a, BIO *b, BIO *c); +void q_SSL_set_accept_state(SSL *a); +void q_SSL_set_connect_state(SSL *a); +int q_SSL_shutdown(SSL *a); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +const SSL_METHOD *q_SSLv2_client_method(); +const SSL_METHOD *q_SSLv3_client_method(); +const SSL_METHOD *q_SSLv23_client_method(); +const SSL_METHOD *q_TLSv1_client_method(); +const SSL_METHOD *q_SSLv2_server_method(); +const SSL_METHOD *q_SSLv3_server_method(); +const SSL_METHOD *q_SSLv23_server_method(); +const SSL_METHOD *q_TLSv1_server_method(); +#else +SSL_METHOD *q_SSLv2_client_method(); +SSL_METHOD *q_SSLv3_client_method(); +SSL_METHOD *q_SSLv23_client_method(); +SSL_METHOD *q_TLSv1_client_method(); +SSL_METHOD *q_SSLv2_server_method(); +SSL_METHOD *q_SSLv3_server_method(); +SSL_METHOD *q_SSLv23_server_method(); +SSL_METHOD *q_TLSv1_server_method(); +#endif +int q_SSL_write(SSL *a, const void *b, int c); +int q_X509_cmp(X509 *a, X509 *b); +#ifdef SSLEAY_MACROS +void *q_ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, char *x); +#define q_X509_dup(x509) (X509 *)q_ASN1_dup((i2d_of_void *)q_i2d_X509, \ + (d2i_of_void *)q_d2i_X509,(char *)x509) +#else +X509 *q_X509_dup(X509 *a); +#endif +ASN1_OBJECT *q_X509_EXTENSION_get_object(X509_EXTENSION *a); +void q_X509_free(X509 *a); +X509_EXTENSION *q_X509_get_ext(X509 *a, int b); +int q_X509_get_ext_count(X509 *a); +void *q_X509_get_ext_d2i(X509 *a, int b, int *c, int *d); +X509_NAME *q_X509_get_issuer_name(X509 *a); +X509_NAME *q_X509_get_subject_name(X509 *a); +int q_X509_verify_cert(X509_STORE_CTX *ctx); +char *q_X509_NAME_oneline(X509_NAME *a, char *b, int c); +EVP_PKEY *q_X509_PUBKEY_get(X509_PUBKEY *a); +void q_X509_STORE_free(X509_STORE *store); +X509_STORE *q_X509_STORE_new(); +int q_X509_STORE_add_cert(X509_STORE *ctx, X509 *x); +void q_X509_STORE_CTX_free(X509_STORE_CTX *storeCtx); +int q_X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, + X509 *x509, STACK_OF(X509) *chain); +X509_STORE_CTX *q_X509_STORE_CTX_new(); +int q_X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); + +#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) +#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) +#ifdef SSLEAY_MACROS +int q_i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); +int q_i2d_RSAPrivateKey(const RSA *a, unsigned char **pp); +RSA *q_d2i_RSAPrivateKey(RSA **a, unsigned char **pp, long length); +DSA *q_d2i_DSAPrivateKey(DSA **a, unsigned char **pp, long length); +#define q_PEM_read_bio_RSAPrivateKey(bp, x, cb, u) \ + (RSA *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_RSAPrivateKey, PEM_STRING_RSA, bp, (void **)x, cb, u) +#define q_PEM_read_bio_DSAPrivateKey(bp, x, cb, u) \ + (DSA *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_DSAPrivateKey, PEM_STRING_DSA, bp, (void **)x, cb, u) +#define q_PEM_write_bio_RSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_RSAPrivateKey,PEM_STRING_RSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_write_bio_DSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_DSAPrivateKey,PEM_STRING_DSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) +#endif +#define q_SSL_CTX_set_options(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL) +#define q_SKM_sk_num(type, st) ((int (*)(const STACK_OF(type) *))q_sk_num)(st) +#define q_SKM_sk_value(type, st,i) ((type * (*)(const STACK_OF(type) *, int))q_sk_value)(st, i) +#define q_sk_GENERAL_NAME_num(st) q_SKM_sk_num(GENERAL_NAME, (st)) +#define q_sk_GENERAL_NAME_value(st, i) q_SKM_sk_value(GENERAL_NAME, (st), (i)) +#define q_sk_X509_num(st) q_SKM_sk_num(X509, (st)) +#define q_sk_X509_value(st, i) q_SKM_sk_value(X509, (st), (i)) +#define q_sk_SSL_CIPHER_num(st) q_SKM_sk_num(SSL_CIPHER, (st)) +#define q_sk_SSL_CIPHER_value(st, i) q_SKM_sk_value(SSL_CIPHER, (st), (i)) +#define q_SSL_CTX_add_extra_chain_cert(ctx,x509) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define q_X509_get_notAfter(x) X509_get_notAfter(x) +#define q_X509_get_notBefore(x) X509_get_notBefore(x) +#define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ + (char *)(rsa)) +#define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ + (char *)(dsa)) +#ifdef OPENSSL_LOAD_CONF +#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() +#else +#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_noconf() +#endif +void q_OPENSSL_add_all_algorithms_noconf(); +void q_OPENSSL_add_all_algorithms_conf(); +int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath); +long q_SSLeay(); + +// Helper function +class QDateTime; +QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime); + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h new file mode 100644 index 0000000000..4662c56ec4 --- /dev/null +++ b/src/network/ssl/qsslsocket_p.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** 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 QSSLSOCKET_P_H +#define QSSLSOCKET_P_H + +#include "qsslsocket.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qtcpsocket_p.h> +#include "qsslkey.h" +#include "qsslconfiguration_p.h" + +#include <QtCore/qstringlist.h> + +#include <private/qringbuffer_p.h> + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_MAC) +#include <Security/SecCertificate.h> +#include <CoreFoundation/CFArray.h> + typedef OSStatus (*PtrSecCertificateGetData)(SecCertificateRef, CSSM_DATA_PTR); + typedef OSStatus (*PtrSecTrustSettingsCopyCertificates)(int, CFArrayRef*); + typedef OSStatus (*PtrSecTrustCopyAnchorCertificates)(CFArrayRef*); +#elif defined(Q_OS_WIN) +#include <windows.h> +#include <wincrypt.h> +#ifndef HCRYPTPROV_LEGACY +#define HCRYPTPROV_LEGACY HCRYPTPROV +#endif +#if defined(Q_OS_WINCE) + typedef HCERTSTORE (WINAPI *PtrCertOpenSystemStoreW)(LPCSTR, DWORD, HCRYPTPROV_LEGACY, DWORD, const void*); +#else + typedef HCERTSTORE (WINAPI *PtrCertOpenSystemStoreW)(HCRYPTPROV_LEGACY, LPCWSTR); +#endif + typedef PCCERT_CONTEXT (WINAPI *PtrCertFindCertificateInStore)(HCERTSTORE, DWORD, DWORD, DWORD, const void*, PCCERT_CONTEXT); + typedef BOOL (WINAPI *PtrCertCloseStore)(HCERTSTORE, DWORD); +#endif + + + +class QSslSocketPrivate : public QTcpSocketPrivate +{ + Q_DECLARE_PUBLIC(QSslSocket) +public: + QSslSocketPrivate(); + virtual ~QSslSocketPrivate(); + + void init(); + bool initialized; + + QSslSocket::SslMode mode; + bool autoStartHandshake; + bool connectionEncrypted; + bool ignoreAllSslErrors; + QList<QSslError> ignoreErrorsList; + bool* readyReadEmittedPointer; + + QSslConfigurationPrivate configuration; + QList<QSslError> sslErrors; + + // if set, this hostname is used for certificate validation instead of the hostname + // that was used for connecting to. + QString verificationPeerName; + + bool allowRootCertOnDemandLoading; + + static bool supportsSsl(); + static void ensureInitialized(); + static void deinitialize(); + static QList<QSslCipher> defaultCiphers(); + static QList<QSslCipher> supportedCiphers(); + static void setDefaultCiphers(const QList<QSslCipher> &ciphers); + static void setDefaultSupportedCiphers(const QList<QSslCipher> &ciphers); + static void resetDefaultCiphers(); + + static QList<QSslCertificate> defaultCaCertificates(); + static QList<QSslCertificate> systemCaCertificates(); + static void setDefaultCaCertificates(const QList<QSslCertificate> &certs); + static bool addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat format, + QRegExp::PatternSyntax syntax); + static void addDefaultCaCertificate(const QSslCertificate &cert); + static void addDefaultCaCertificates(const QList<QSslCertificate> &certs); + +#if defined(Q_OS_MAC) + static PtrSecCertificateGetData ptrSecCertificateGetData; + static PtrSecTrustSettingsCopyCertificates ptrSecTrustSettingsCopyCertificates; + static PtrSecTrustCopyAnchorCertificates ptrSecTrustCopyAnchorCertificates; +#elif defined(Q_OS_WIN) + static PtrCertOpenSystemStoreW ptrCertOpenSystemStoreW; + static PtrCertFindCertificateInStore ptrCertFindCertificateInStore; + static PtrCertCloseStore ptrCertCloseStore; +#endif + + // The socket itself, including private slots. + QTcpSocket *plainSocket; + void createPlainSocket(QIODevice::OpenMode openMode); + static void pauseSocketNotifiers(QSslSocket*); + static void resumeSocketNotifiers(QSslSocket*); + void _q_connectedSlot(); + void _q_hostFoundSlot(); + void _q_disconnectedSlot(); + void _q_stateChangedSlot(QAbstractSocket::SocketState); + void _q_errorSlot(QAbstractSocket::SocketError); + void _q_readyReadSlot(); + void _q_bytesWrittenSlot(qint64); + void _q_flushWriteBuffer(); + void _q_flushReadBuffer(); + + // Platform specific functions + virtual void startClientEncryption() = 0; + virtual void startServerEncryption() = 0; + virtual void transmit() = 0; + virtual void disconnectFromHost() = 0; + virtual void disconnected() = 0; + virtual QSslCipher sessionCipher() const = 0; + +private: + static bool ensureLibraryLoaded(); + static void ensureCiphersAndCertsLoaded(); + + static bool s_libraryLoaded; + static bool s_loadedCiphersAndCerts; +protected: + static bool s_loadRootCertsOnDemand; + static QList<QByteArray> unixRootCertDirectories(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri new file mode 100644 index 0000000000..8b2e2c1917 --- /dev/null +++ b/src/network/ssl/ssl.pri @@ -0,0 +1,36 @@ +# OpenSSL support; compile in QSslSocket. +contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) { + + +symbian { + INCLUDEPATH *= $$OS_LAYER_SSL_SYSTEMINCLUDE +} else { + include($$QT_SOURCE_TREE/config.tests/unix/openssl/openssl.pri) +} + + HEADERS += ssl/qssl.h \ + ssl/qsslcertificate.h \ + ssl/qsslcertificate_p.h \ + ssl/qsslconfiguration.h \ + ssl/qsslconfiguration_p.h \ + ssl/qsslcipher.h \ + ssl/qsslcipher_p.h \ + ssl/qsslerror.h \ + ssl/qsslkey.h \ + ssl/qsslsocket.h \ + ssl/qsslsocket_openssl_p.h \ + ssl/qsslsocket_openssl_symbols_p.h \ + ssl/qsslsocket_p.h + SOURCES += ssl/qssl.cpp \ + ssl/qsslcertificate.cpp \ + ssl/qsslconfiguration.cpp \ + ssl/qsslcipher.cpp \ + ssl/qsslerror.cpp \ + ssl/qsslkey.cpp \ + ssl/qsslsocket.cpp \ + ssl/qsslsocket_openssl.cpp \ + ssl/qsslsocket_openssl_symbols.cpp + + # Add optional SSL libs + LIBS_PRIVATE += $$OPENSSL_LIBS +} |