diff -r 000000000000 -r 1918ee327afb src/network/access/qnetworkaccessmanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/network/access/qnetworkaccessmanager.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,991 @@ +/**************************************************************************** +** +** Copyright (C) 2009 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 "qnetworkaccesshttpbackend_p.h" +#include "qnetworkaccessftpbackend_p.h" +#include "qnetworkaccessfilebackend_p.h" +#include "qnetworkaccessdatabackend_p.h" +#include "qnetworkaccessdebugpipebackend_p.h" + +#include "QtCore/qbuffer.h" +#include "QtCore/qurl.h" +#include "QtCore/qvector.h" +#include "QtNetwork/qauthenticator.h" +#include "QtNetwork/qsslconfiguration.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_HTTP +Q_GLOBAL_STATIC(QNetworkAccessHttpBackendFactory, httpBackend) +#endif // QT_NO_HTTP +Q_GLOBAL_STATIC(QNetworkAccessFileBackendFactory, fileBackend) +Q_GLOBAL_STATIC(QNetworkAccessDataBackendFactory, dataBackend) +#ifndef QT_NO_FTP +Q_GLOBAL_STATIC(QNetworkAccessFtpBackendFactory, ftpBackend) +#endif // QT_NO_FTP + +#ifdef QT_BUILD_INTERNAL +Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend) +#endif + +static void ensureInitialized() +{ +#ifndef QT_NO_HTTP + (void) httpBackend(); +#endif // QT_NO_HTTP + (void) dataBackend(); +#ifndef QT_NO_FTP + (void) ftpBackend(); +#endif + +#ifdef QT_BUILD_INTERNAL + (void) debugpipeBackend(); +#endif + + // leave this one last since it will query the special QAbstractFileEngines + (void) fileBackend(); +} + +/*! + \class QNetworkAccessManager + \brief The QNetworkAccessManager class allows the application to + 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. + + 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 + + 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. + + A more involved example, assuming the manager is already existent, + can be: + \snippet doc/src/snippets/code/src_network_access_qnetworkaccessmanager.cpp 1 + + \sa QNetworkRequest, QNetworkReply, QNetworkProxy +*/ + +/*! + \enum QNetworkAccessManager::Operation + + Indicates the operation this reply is processing. + + \value HeadOperation retrieve headers operation (created + with head()) + + \value GetOperation retrieve headers and download contents + (created with get()) + + \value PutOperation upload contents operation (created + with put()) + + \value PostOperation send the contents of an HTML form for + processing via HTTP POST (created with post()) + + \value DeleteOperation delete contents operation (created with + deleteResource()) + + \omitvalue UnknownOperation + + \sa QNetworkReply::operation() +*/ + +/*! + \fn void QNetworkAccessManager::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + + This signal is emitted whenever a proxy requests authentication + and QNetworkAccessManager cannot find a valid, cached + credential. The slot connected to this signal should fill in the + credentials for the proxy \a proxy in the \a authenticator object. + + QNetworkAccessManager will cache the credentials internally. The + next time the proxy requests authentication, QNetworkAccessManager + will automatically send the same credential without emitting the + proxyAuthenticationRequired signal again. + + If the proxy rejects the credentials, QNetworkAccessManager will + emit the signal again. + + \sa proxy(), setProxy(), authenticationRequired() +*/ + +/*! + \fn void QNetworkAccessManager::authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) + + This signal is emitted whenever a final server requests + authentication before it delivers the requested contents. The slot + connected to this signal should fill the credentials for the + contents (which can be determined by inspecting the \a reply + object) in the \a authenticator object. + + QNetworkAccessManager will cache the credentials internally and + will send the same values if the server requires authentication + again, without emitting the authenticationRequired() signal. If it + rejects the credentials, this signal will be emitted again. + + \sa proxyAuthenticationRequired() +*/ + +/*! + \fn void QNetworkAccessManager::finished(QNetworkReply *reply) + + This signal is emitted whenever a pending network reply is + finished. The \a reply parameter will contain a pointer to the + reply that has just finished. This signal is emitted in tandem + with the QNetworkReply::finished() signal. + + See QNetworkReply::finished() for information on the status that + the object will be in. + + \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 &errors) + + This signal is emitted if the SSL/TLS session encountered errors + during the set up, including certificate verification errors. The + \a errors parameter contains the list of errors and \a reply is + the QNetworkReply that is encountering these errors. + + To indicate that the errors are not fatal and that the connection + should proceed, the QNetworkReply::ignoreSslErrors() function should be called + from the slot connected to this signal. If it is not called, the + SSL session will be torn down before any data is exchanged + (including the URL). + + This signal can be used to display an error message to the user + indicating that security may be compromised and display the + SSL settings (see sslConfiguration() to obtain it). If the user + decides to proceed after analyzing the remote certificate, the + slot should call ignoreSslErrors(). + + \sa QSslSocket::sslErrors(), QNetworkReply::sslErrors(), + QNetworkReply::sslConfiguration(), QNetworkReply::ignoreSslErrors() +*/ + +class QNetworkAuthenticationCredential +{ +public: + QString domain; + QString user; + QString password; +}; +Q_DECLARE_TYPEINFO(QNetworkAuthenticationCredential, Q_MOVABLE_TYPE); +inline bool operator<(const QNetworkAuthenticationCredential &t1, const QString &t2) +{ return t1.domain < t2; } + +class QNetworkAuthenticationCache: private QVector, + 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::insert(++closestMatch, newCredential); + else + QVector::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); +} + +/*! + Constructs a QNetworkAccessManager object that is the center of + the Network Access API and sets \a parent as the parent object. +*/ +QNetworkAccessManager::QNetworkAccessManager(QObject *parent) + : QObject(*new QNetworkAccessManagerPrivate, parent) +{ + ensureInitialized(); +} + +/*! + Destroys the QNetworkAccessManager object and frees up any + resources. Note that QNetworkReply objects that are returned from + this class have this object set as their parents, which means that + they will be deleted along with it if you don't call + QObject::setParent() on them. +*/ +QNetworkAccessManager::~QNetworkAccessManager() +{ +#ifndef QT_NO_NETWORKPROXY + delete d_func()->proxyFactory; +#endif +} + +#ifndef QT_NO_NETWORKPROXY +/*! + Returns the QNetworkProxy that the requests sent using this + QNetworkAccessManager object will use. The default value for the + proxy is QNetworkProxy::DefaultProxy. + + \sa setProxy(), setProxyFactory(), proxyAuthenticationRequired() +*/ +QNetworkProxy QNetworkAccessManager::proxy() const +{ + return d_func()->proxy; +} + +/*! + Sets the proxy to be used in future requests to be \a proxy. This + does not affect requests that have already been sent. The + proxyAuthenticationRequired() signal will be emitted if the proxy + requests authentication. + + A proxy set with this function will be used for all requests + issued by QNetworkAccessManager. In some cases, it might be + necessary to select different proxies depending on the type of + request being sent or the destination host. If that's the case, + you should consider using setProxyFactory(). + + \sa proxy(), proxyAuthenticationRequired() +*/ +void QNetworkAccessManager::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QNetworkAccessManager); + delete d->proxyFactory; + d->proxy = proxy; + d->proxyFactory = 0; +} + +/*! + \fn QNetworkProxyFactory *QNetworkAccessManager::proxyFactory() const + \since 4.5 + + Returns the proxy factory that this QNetworkAccessManager object + is using to determine the proxies to be used for requests. + + Note that the pointer returned by this function is managed by + QNetworkAccessManager and could be deleted at any time. + + \sa setProxyFactory(), proxy() +*/ +QNetworkProxyFactory *QNetworkAccessManager::proxyFactory() const +{ + return d_func()->proxyFactory; +} + +/*! + \since 4.5 + + Sets the proxy factory for this class to be \a factory. A proxy + factory is used to determine a more specific list of proxies to be + used for a given request, instead of trying to use the same proxy + value for all requests. + + All queries sent by QNetworkAccessManager will have type + QNetworkProxyQuery::UrlRequest. + + For example, a proxy factory could apply the following rules: + \list + \o if the target address is in the local network (for example, + if the hostname contains no dots or if it's an IP address in + the organization's range), return QNetworkProxy::NoProxy + \o if the request is FTP, return an FTP proxy + \o if the request is HTTP or HTTPS, then return an HTTP proxy + \o otherwise, return a SOCKSv5 proxy server + \endlist + + The lifetime of the object \a factory will be managed by + QNetworkAccessManager. It will delete the object when necessary. + + \note If a specific proxy is set with setProxy(), the factory will not + be used. + + \sa proxyFactory(), setProxy(), QNetworkProxyQuery +*/ +void QNetworkAccessManager::setProxyFactory(QNetworkProxyFactory *factory) +{ + Q_D(QNetworkAccessManager); + delete d->proxyFactory; + d->proxyFactory = factory; + d->proxy = QNetworkProxy(); +} +#endif + +/*! + \since 4.5 + + Returns the cache that is used to store data obtained from the network. + + \sa setCache() +*/ +QAbstractNetworkCache *QNetworkAccessManager::cache() const +{ + Q_D(const QNetworkAccessManager); + return d->networkCache; +} + +/*! + \since 4.5 + + Sets the manager's network cache to be the \a cache specified. The cache + is used for all requests dispatched by the manager. + + Use this function to set the network cache object to a class that implements + additional features, like saving the cookies to permanent storage. + + \note QNetworkAccessManager takes ownership of the \a cache object. + + QNetworkAccessManager by default does not have a set cache. + Qt provides a simple disk cache, QNetworkDiskCache, which can be used. + + \sa cache(), QNetworkRequest::CacheLoadControl +*/ +void QNetworkAccessManager::setCache(QAbstractNetworkCache *cache) +{ + Q_D(QNetworkAccessManager); + if (d->networkCache != cache) { + delete d->networkCache; + d->networkCache = cache; + 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. + + QNetworkAccessManager will set the parent of the \a cookieJar + passed to itself, so that the cookie jar is deleted when this + object is deleted as well. If you want to share cookie jars + between different QNetworkAccessManager objects, you may want to + set the cookie jar's parent to 0 after calling this function. + + QNetworkAccessManager by default does not implement any cookie + policy of its own: it accepts all cookies sent by the server, as + long as they are well formed and meet the minimum security + requirements (cookie domain matches the request's and cookie path + matches the request's). In order to implement your own security + policy, override the QNetworkCookieJar::cookiesForUrl() and + QNetworkCookieJar::setCookiesFromUrl() virtual functions. Those + functions are called by QNetworkAccessManager when it detects a + new cookie. + + \sa cookieJar(), QNetworkCookieJar::cookiesForUrl(), QNetworkCookieJar::setCookiesFromUrl() +*/ +void QNetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar) +{ + Q_D(QNetworkAccessManager); + d->cookieJarCreated = true; + if (d->cookieJar != cookieJar) { + if (d->cookieJar && d->cookieJar->parent() == this) + delete d->cookieJar; + d->cookieJar = cookieJar; + d->cookieJar->setParent(this); + } +} + +/*! + 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() +*/ +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() +*/ +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; +} + +/*! + 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() +*/ +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() +*/ +QNetworkReply *QNetworkAccessManager::deleteResource(const QNetworkRequest &request) +{ + return d_func()->postProcess(createRequest(QNetworkAccessManager::DeleteOperation, request)); +} + +/*! + Returns a new QNetworkReply object to handle the operation \a op + and request \a req. The device \a outgoingData is always 0 for Get and + Head requests, but is the value passed to post() and put() in + those operations (the QByteArray variants will pass a QBuffer + object). + + The default implementation calls QNetworkCookieJar::cookiesForUrl() + on the cookie jar set with setCookieJar() to obtain the cookies to + be sent to the remote server. + + The returned object must be in an open state. +*/ +QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, + const QNetworkRequest &req, + QIODevice *outgoingData) +{ + Q_D(QNetworkAccessManager); + QNetworkRequest request = req; + if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() && + outgoingData && !outgoingData->isSequential()) { + // request has no Content-Length + // but the data that is outgoing is random-access + request.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData->size()); + } + if (d->cookieJar) { + QList cookies = d->cookieJar->cookiesForUrl(request.url()); + if (!cookies.isEmpty()) + request.setHeader(QNetworkRequest::CookieHeader, qVariantFromValue(cookies)); + } + + // first step: create the reply + QUrl url = request.url(); + QNetworkReplyImpl *reply = new QNetworkReplyImpl(this); + QNetworkReplyImplPrivate *priv = reply->d_func(); + priv->manager = this; + + // second step: fetch cached credentials + QNetworkAuthenticationCredential *cred = d->fetchCachedCredentials(url); + if (cred) { + url.setUserName(cred->user); + url.setPassword(cred->password); + priv->urlForLastAuthentication = url; + } + + // third step: find a backend + priv->backend = d->findBackend(op, request); + + // fourth step: setup the reply + priv->setup(op, request, outgoingData); +#ifndef QT_NO_NETWORKPROXY + QList 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 + return reply; +} + +void QNetworkAccessManagerPrivate::_q_replyFinished() +{ + Q_Q(QNetworkAccessManager); + QNetworkReply *reply = qobject_cast(q->sender()); + if (reply) + emit q->finished(reply); +} + +void QNetworkAccessManagerPrivate::_q_replySslErrors(const QList &errors) +{ +#ifndef QT_NO_OPENSSL + Q_Q(QNetworkAccessManager); + QNetworkReply *reply = qobject_cast(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)), SLOT(_q_replySslErrors(QList))); +#endif + + return reply; +} + +void QNetworkAccessManagerPrivate::createCookieJar() const +{ + if (!cookieJarCreated) { + // keep the ugly hack in here + QNetworkAccessManagerPrivate *that = const_cast(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 + if (url != backend->reply->urlForLastAuthentication) { + QNetworkAuthenticationCredential *cred = fetchCachedCredentials(url, authenticator); + if (cred) { + authenticator->setUser(cred->user); + authenticator->setPassword(cred->password); + backend->reply->urlForLastAuthentication = url; + return; + } + } + + backend->reply->urlForLastAuthentication = url; + emit q->authenticationRequired(backend->reply->q_func(), authenticator); + addCredentials(url, authenticator); +} + +#ifndef QT_NO_NETWORKPROXY +void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBackend *backend, + const QNetworkProxy &proxy, + QAuthenticator *authenticator) +{ + Q_Q(QNetworkAccessManager); + // ### 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 = fetchCachedCredentials(proxy); + if (cred) { + authenticator->setUser(cred->user); + authenticator->setPassword(cred->password); + return; + } + } + + backend->reply->lastProxyAuthentication = proxy; + emit q->proxyAuthenticationRequired(proxy, authenticator); + addCredentials(proxy, authenticator); +} + +void QNetworkAccessManagerPrivate::addCredentials(const QNetworkProxy &p, + const QAuthenticator *authenticator) +{ + Q_ASSERT(authenticator); + Q_ASSERT(p.type() != QNetworkProxy::DefaultProxy); + Q_ASSERT(p.type() != QNetworkProxy::NoProxy); + + QString realm = authenticator->realm(); + QNetworkProxy proxy = p; + proxy.setUser(authenticator->user()); + // Set two credentials: one with the username and one without + do { + // Set two credentials actually: one with and one without the realm + do { + QByteArray cacheKey = proxyAuthenticationKey(proxy, realm); + if (cacheKey.isEmpty()) + return; // should not happen + + QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache; + auth->insert(QString(), authenticator->user(), authenticator->password()); + objectCache.addEntry(cacheKey, auth); // replace the existing one, if there's any + + if (realm.isEmpty()) { + break; + } else { + realm.clear(); + } + } while (true); + + if (proxy.user().isEmpty()) + break; + else + proxy.setUser(QString()); + } while (true); +} + +QNetworkAuthenticationCredential * +QNetworkAccessManagerPrivate::fetchCachedCredentials(const QNetworkProxy &p, + const QAuthenticator *authenticator) +{ + QNetworkProxy proxy = p; + if (proxy.type() == QNetworkProxy::DefaultProxy) { + proxy = QNetworkProxy::applicationProxy(); + } + if (!proxy.password().isEmpty()) + return 0; // no need to set credentials if it already has them + + QString realm; + if (authenticator) + realm = authenticator->realm(); + + QByteArray cacheKey = proxyAuthenticationKey(proxy, realm); + if (cacheKey.isEmpty()) + return 0; + if (!objectCache.hasEntry(cacheKey)) + return 0; + + QNetworkAuthenticationCache *auth = + static_cast(objectCache.requestEntryNow(cacheKey)); + QNetworkAuthenticationCredential *cred = auth->findClosestMatch(QString()); + objectCache.releaseEntry(cacheKey); + + // proxy cache credentials always have exactly one item + Q_ASSERT_X(cred, "QNetworkAccessManager", + "Internal inconsistency: found a cache key for a proxy, but it's empty"); + return cred; +} + +QList QNetworkAccessManagerPrivate::queryProxy(const QNetworkProxyQuery &query) +{ + QList proxies; + if (proxyFactory) { + proxies = proxyFactory->queryProxy(query); + if (proxies.isEmpty()) { + qWarning("QNetworkAccessManager: factory %p has returned an empty result set", + proxyFactory); + proxies << QNetworkProxy::NoProxy; + } + } else if (proxy.type() == QNetworkProxy::DefaultProxy) { + // no proxy set, query the application + return QNetworkProxyFactory::proxyForQuery(query); + } else { + proxies << proxy; + } + + return proxies; +} +#endif + +void QNetworkAccessManagerPrivate::addCredentials(const QUrl &url, + const QAuthenticator *authenticator) +{ + Q_ASSERT(authenticator); + QString domain = QString::fromLatin1("/"); // FIXME: make QAuthenticator return the domain + QString realm = authenticator->realm(); + + // Set two credentials actually: one with and one without the username in the URL + QUrl copy = url; + copy.setUserName(authenticator->user()); + do { + QByteArray cacheKey = authenticationKey(copy, realm); + if (objectCache.hasEntry(cacheKey)) { + QNetworkAuthenticationCache *auth = + static_cast(objectCache.requestEntryNow(cacheKey)); + auth->insert(domain, authenticator->user(), authenticator->password()); + objectCache.releaseEntry(cacheKey); + } else { + QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache; + auth->insert(domain, authenticator->user(), authenticator->password()); + objectCache.addEntry(cacheKey, auth); + } + + if (copy.userName().isEmpty()) { + break; + } else { + copy.setUserName(QString()); + } + } while (true); +} + +/*! + Fetch the credential data from the credential cache. + + If auth is 0 (as it is when called from createRequest()), this will try to + look up with an empty realm. That fails in most cases for HTTP (because the + realm is seldom empty for HTTP challenges). In any case, QHttpNetworkConnection + never sends the credentials on the first attempt: it needs to find out what + authentication methods the server supports. + + For FTP, realm is always empty. +*/ +QNetworkAuthenticationCredential * +QNetworkAccessManagerPrivate::fetchCachedCredentials(const QUrl &url, + const QAuthenticator *authentication) +{ + if (!url.password().isEmpty()) + return 0; // no need to set credentials if it already has them + + QString realm; + if (authentication) + realm = authentication->realm(); + + QByteArray cacheKey = authenticationKey(url, realm); + if (!objectCache.hasEntry(cacheKey)) + return 0; + + QNetworkAuthenticationCache *auth = + static_cast(objectCache.requestEntryNow(cacheKey)); + QNetworkAuthenticationCredential *cred = auth->findClosestMatch(url.path()); + objectCache.releaseEntry(cacheKey); + return cred; +} + +void QNetworkAccessManagerPrivate::clearCache(QNetworkAccessManager *manager) +{ + manager->d_func()->objectCache.clear(); +} + +QNetworkAccessManagerPrivate::~QNetworkAccessManagerPrivate() +{ +} + +QT_END_NAMESPACE + +#include "moc_qnetworkaccessmanager.cpp"