summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/access/access.pri4
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp10
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h1
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp11
-rw-r--r--src/network/access/qnetworkaccessbackend.cpp80
-rw-r--r--src/network/access/qnetworkaccesscachebackend.cpp9
-rw-r--r--src/network/access/qnetworkaccesshttpbackend.cpp12
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp73
-rw-r--r--src/network/access/qnetworkaccessmanager.h1
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h12
-rw-r--r--src/network/access/qnetworkcookie.cpp9
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp1983
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h309
-rw-r--r--src/network/access/qnetworkreplyimpl.cpp26
-rw-r--r--src/network/access/qnetworkrequest.cpp7
-rw-r--r--src/network/bearer/qnetworkconfigmanager_p.cpp15
-rw-r--r--src/network/kernel/qhostinfo.cpp2
-rw-r--r--src/network/kernel/qnetworkproxy.cpp98
-rw-r--r--src/network/kernel/qnetworkproxy.h16
-rw-r--r--src/network/kernel/qnetworkproxy_symbian.cpp13
-rw-r--r--src/network/socket/qabstractsocket.cpp4
-rw-r--r--src/network/socket/qhttpsocketengine.cpp8
-rw-r--r--src/network/socket/qsocks5socketengine.cpp7
-rw-r--r--src/network/ssl/qsslconfiguration.cpp4
-rw-r--r--src/network/ssl/qsslsocket.cpp2
25 files changed, 2618 insertions, 98 deletions
diff --git a/src/network/access/access.pri b/src/network/access/access.pri
index 5ead3ad37f..0f901b873d 100644
--- a/src/network/access/access.pri
+++ b/src/network/access/access.pri
@@ -14,7 +14,6 @@ HEADERS += \
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 \
@@ -29,6 +28,7 @@ HEADERS += \
access/qnetworkreply_p.h \
access/qnetworkreplyimpl_p.h \
access/qnetworkreplydataimpl_p.h \
+ access/qnetworkreplyhttpimpl_p.h \
access/qnetworkreplyfileimpl_p.h \
access/qabstractnetworkcache_p.h \
access/qabstractnetworkcache.h \
@@ -54,13 +54,13 @@ SOURCES += \
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/qnetworkreplyhttpimpl.cpp \
access/qnetworkreplyfileimpl.cpp \
access/qabstractnetworkcache.cpp \
access/qnetworkdiskcache.cpp \
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 83156c6a35..a47155906f 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -117,7 +117,6 @@ QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
void QHttpNetworkConnectionPrivate::init()
{
- Q_Q(QHttpNetworkConnection);
for (int i = 0; i < channelCount; i++) {
channels[i].setConnection(this->q_func());
channels[i].ssl = encrypt;
@@ -520,6 +519,15 @@ bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket)
return false;
}
+QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest()
+{
+ if (!highPriorityQueue.isEmpty())
+ return highPriorityQueue.last().first;
+ if (!lowPriorityQueue.isEmpty())
+ return lowPriorityQueue.last().first;
+ return QHttpNetworkRequest();
+}
+
// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
{
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index adb779f473..329d3626fa 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -169,6 +169,7 @@ public:
void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke
bool dequeueRequest(QAbstractSocket *socket);
void prepareRequest(HttpMessagePair &request);
+ QHttpNetworkRequest predictNextRequest();
void fillPipeline(QAbstractSocket *socket);
bool fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel);
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 6fbc6f8056..f44010100f 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -579,6 +579,17 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
connectHost = connection->d_func()->networkProxy.hostName();
connectPort = connection->d_func()->networkProxy.port();
}
+ if (socket->proxy().type() == QNetworkProxy::HttpProxy) {
+ // Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
+ QByteArray value;
+ // ensureConnection is called before any request has been assigned, but can also be called again if reconnecting
+ if (request.url().isEmpty())
+ value = connection->d_func()->predictNextRequest().headerField("user-agent");
+ else
+ value = request.headerField("user-agent");
+ if (!value.isEmpty())
+ socket->setProperty("_q_user-agent", value);
+ }
#endif
if (ssl) {
#ifndef QT_NO_OPENSSL
diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp
index 6220abed02..a0042f436b 100644
--- a/src/network/access/qnetworkaccessbackend.cpp
+++ b/src/network/access/qnetworkaccessbackend.cpp
@@ -41,6 +41,7 @@
#include "qnetworkaccessbackend_p.h"
#include "qnetworkaccessmanager_p.h"
+#include "qnetworkconfigmanager.h"
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
#include "qnetworkreply_p.h"
@@ -315,13 +316,13 @@ void QNetworkAccessBackend::error(QNetworkReply::NetworkError code, const QStrin
void QNetworkAccessBackend::proxyAuthenticationRequired(const QNetworkProxy &proxy,
QAuthenticator *authenticator)
{
- manager->proxyAuthenticationRequired(this, proxy, authenticator);
+ manager->proxyAuthenticationRequired(proxy, synchronous, authenticator, &reply->lastProxyAuthentication);
}
#endif
void QNetworkAccessBackend::authenticationRequired(QAuthenticator *authenticator)
{
- manager->authenticationRequired(this, authenticator);
+ manager->authenticationRequired(authenticator, reply->q_func(), synchronous, reply->url, &reply->urlForLastAuthentication);
}
void QNetworkAccessBackend::metaDataChanged()
@@ -343,8 +344,6 @@ void QNetworkAccessBackend::sslErrors(const QList<QSslError> &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
@@ -352,31 +351,62 @@ void QNetworkAccessBackend::sslErrors(const QList<QSslError> &errors)
*/
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;
+#ifndef QT_NO_BEARERMANAGEMENT
+ // For bearer, check if session start is required
+ if (manager->networkSession) {
+ // session required
+ if (manager->networkSession->isOpen() &&
+ manager->networkSession->state() == QNetworkSession::Connected) {
+ // Session is already open and ready to use.
+ // copy network session down to the backend
+ setProperty("_q_networksession", QVariant::fromValue(manager->networkSession));
+ } else {
+ // Session not ready, but can skip for loopback connections
+
+ // 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.
+ } else {
+ // need to wait for session to be opened
+ return false;
+ }
+ }
}
+#endif
- 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;
+#ifndef QT_NO_NETWORKPROXY
+#ifndef QT_NO_BEARERMANAGEMENT
+ // Get the proxy settings from the network session (in the case of service networks,
+ // the proxy settings change depending which AP was activated)
+ QNetworkSession *session = manager->networkSession.data();
+ QNetworkConfiguration config;
+ if (session) {
+ QNetworkConfigurationManager configManager;
+ // The active configuration tells us what IAP is in use
+ QVariant v = session->sessionProperty(QLatin1String("ActiveConfiguration"));
+ if (v.isValid())
+ config = configManager.configurationFromIdentifier(qvariant_cast<QString>(v));
+ // Fallback to using the configuration if no active configuration
+ if (!config.isValid())
+ config = session->configuration();
+ // or unspecified configuration if that is no good either
+ if (!config.isValid())
+ config = QNetworkConfiguration();
}
+ reply->proxyList = manager->queryProxy(QNetworkProxyQuery(config, url()));
+#else // QT_NO_BEARERMANAGEMENT
+ // Without bearer management, the proxy depends only on the url
+ reply->proxyList = manager->queryProxy(QNetworkProxyQuery(url()));
+#endif
+#endif
- return false;
+ // now start the request
+ open();
+ return true;
}
-#endif
QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkaccesscachebackend.cpp b/src/network/access/qnetworkaccesscachebackend.cpp
index 13f4cd9cbb..c585848c98 100644
--- a/src/network/access/qnetworkaccesscachebackend.cpp
+++ b/src/network/access/qnetworkaccesscachebackend.cpp
@@ -66,6 +66,7 @@ void QNetworkAccessCacheBackend::open()
QString msg = QCoreApplication::translate("QNetworkAccessCacheBackend", "Error opening %1")
.arg(this->url().toString());
error(QNetworkReply::ContentNotFoundError, msg);
+ } else {
setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
}
finished();
@@ -85,14 +86,18 @@ bool QNetworkAccessCacheBackend::sendCacheContents()
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)
+ for ( ; it != end; ++it) {
+ if (it->first.toLower() == "cache-control" &&
+ it->second.toLower().contains("must-revalidate")) {
+ return false;
+ }
setRawHeader(it->first, it->second);
+ }
// handle a possible redirect
QVariant redirectionTarget = attributes.value(QNetworkRequest::RedirectionTargetAttribute);
diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp
index c61911445b..a45c2de0b2 100644
--- a/src/network/access/qnetworkaccesshttpbackend.cpp
+++ b/src/network/access/qnetworkaccesshttpbackend.cpp
@@ -262,13 +262,11 @@ bool QNetworkAccessHttpBackend::loadFromCacheIfAllowed(QHttpNetworkRequest &http
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;
- }
+ 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();
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 5a7521e33e..dbaa2d1082 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -67,13 +67,12 @@
#include "QtNetwork/qhttpmultipart.h"
#include "qhttpmultipart_p.h"
+#include "qnetworkreplyhttpimpl_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)
@@ -85,10 +84,6 @@ Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend)
static void ensureInitialized()
{
-#ifndef QT_NO_HTTP
- (void) httpBackend();
-#endif // QT_NO_HTTP
-
#ifndef QT_NO_FTP
(void) ftpBackend();
#endif
@@ -356,6 +351,17 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent)
ensureInitialized();
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
+#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>");
}
/*!
@@ -861,7 +867,7 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess
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.
+ \note This feature is currently available for HTTP(S) only.
\sa get(), post(), put(), deleteResource()
*/
@@ -967,6 +973,18 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
}
}
+#ifndef QT_NO_HTTP
+ // Since Qt 5 we use the new QNetworkReplyHttpImpl
+ if (scheme == QLatin1String("http") || scheme == QLatin1String("https") ) {
+ QNetworkReplyHttpImpl *reply = new QNetworkReplyHttpImpl(this, request, op, outgoingData);
+#ifndef QT_NO_BEARERMANAGEMENT
+ connect(this, SIGNAL(networkSessionConnected()),
+ reply, SLOT(_q_networkSessionConnected()));
+#endif
+ return reply;
+ }
+#endif // QT_NO_HTTP
+
// first step: create the reply
QUrl url = request.url();
QNetworkReplyImpl *reply = new QNetworkReplyImpl(this);
@@ -986,10 +1004,6 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
// 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;
@@ -1055,42 +1069,43 @@ void QNetworkAccessManagerPrivate::createCookieJar() const
}
}
-void QNetworkAccessManagerPrivate::authenticationRequired(QNetworkAccessBackend *backend,
- QAuthenticator *authenticator)
+void QNetworkAccessManagerPrivate::authenticationRequired(QAuthenticator *authenticator,
+ QNetworkReply *reply,
+ bool synchronous,
+ QUrl &url,
+ QUrl *urlForLastAuthentication)
{
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) {
+ if (urlForLastAuthentication->isEmpty()
+ || url != *urlForLastAuthentication) {
QNetworkAuthenticationCredential cred = authenticationManager->fetchCachedCredentials(url, authenticator);
if (!cred.isNull()) {
authenticator->setUser(cred.user);
authenticator->setPassword(cred.password);
- backend->reply->urlForLastAuthentication = url;
+ *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())
+ if (synchronous)
return;
- backend->reply->urlForLastAuthentication = url;
- emit q->authenticationRequired(backend->reply->q_func(), authenticator);
+ *urlForLastAuthentication = url;
+ emit q->authenticationRequired(reply, authenticator);
authenticationManager->cacheCredentials(url, authenticator);
}
#ifndef QT_NO_NETWORKPROXY
-void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBackend *backend,
- const QNetworkProxy &proxy,
- QAuthenticator *authenticator)
+void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
+ bool synchronous,
+ QAuthenticator *authenticator,
+ QNetworkProxy *lastProxyAuthentication)
{
Q_Q(QNetworkAccessManager);
// ### FIXME Tracking of successful authentications
@@ -1100,7 +1115,7 @@ void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBac
// proxyAuthenticationRequired gets emitted again
// possible solution: some tracking inside the authenticator
// or a new function proxyAuthenticationSucceeded(true|false)
- if (proxy != backend->reply->lastProxyAuthentication) {
+ if (proxy != *lastProxyAuthentication) {
QNetworkAuthenticationCredential cred = authenticationManager->fetchCachedProxyCredentials(proxy);
if (!cred.isNull()) {
authenticator->setUser(cred.user);
@@ -1111,10 +1126,10 @@ void QNetworkAccessManagerPrivate::proxyAuthenticationRequired(QNetworkAccessBac
// 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())
+ if (synchronous)
return;
- backend->reply->lastProxyAuthentication = proxy;
+ *lastProxyAuthentication = proxy;
emit q->proxyAuthenticationRequired(proxy, authenticator);
authenticationManager->cacheProxyCredentials(proxy, authenticator);
}
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index 47760b210b..b22be91a96 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -160,6 +160,7 @@ protected:
private:
friend class QNetworkReplyImplPrivate;
friend class QNetworkAccessHttpBackend;
+ friend class QNetworkReplyHttpImpl;
Q_DECLARE_PRIVATE(QNetworkAccessManager)
Q_PRIVATE_SLOT(d_func(), void _q_replyFinished())
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
index f64cc4dc79..f5e13e3a09 100644
--- a/src/network/access/qnetworkaccessmanager_p.h
+++ b/src/network/access/qnetworkaccessmanager_p.h
@@ -94,14 +94,20 @@ public:
QNetworkReply *postProcess(QNetworkReply *reply);
void createCookieJar() const;
- void authenticationRequired(QNetworkAccessBackend *backend, QAuthenticator *authenticator);
+ void authenticationRequired(QAuthenticator *authenticator,
+ QNetworkReply *reply,
+ bool synchronous,
+ QUrl &url,
+ QUrl *urlForLastAuthentication);
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 proxyAuthenticationRequired(const QNetworkProxy &proxy,
+ bool synchronous,
+ QAuthenticator *authenticator,
+ QNetworkProxy *lastProxyAuthentication);
void cacheProxyCredentials(const QNetworkProxy &proxy, const QAuthenticator *auth);
QNetworkAuthenticationCredential *fetchCachedProxyCredentials(const QNetworkProxy &proxy,
const QAuthenticator *auth = 0);
diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp
index 52eb3453b8..eec850797b 100644
--- a/src/network/access/qnetworkcookie.cpp
+++ b/src/network/access/qnetworkcookie.cpp
@@ -395,8 +395,8 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
// 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 it is NAME=VALUE, retain the value as is
+ // refer to http://bugreports.qt.nokia.com/browse/QTBUG-17746
if (isNameValue)
second += '"';
++i;
@@ -432,7 +432,9 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
position = i;
for ( ; i < length; ++i) {
register char c = text.at(i);
- if (c == ',' || c == ';' || isLWS(c))
+ // for name value pairs, we want to parse until reaching the next ';'
+ // and not break when reaching a space char
+ if (c == ',' || c == ';' || ((isNameValue && (c == '\n' || c == '\r')) || (!isNameValue && isLWS(c))))
break;
}
@@ -487,7 +489,6 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const
result += '=';
if ((d->value.contains(';') ||
d->value.contains(',') ||
- d->value.contains(' ') ||
d->value.contains('"')) &&
(!d->value.startsWith('"') &&
!d->value.endsWith('"'))) {
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
new file mode 100644
index 0000000000..cd6d8fb87e
--- /dev/null
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -0,0 +1,1983 @@
+/****************************************************************************
+**
+** 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 "qnetworkreplyhttpimpl_p.h"
+#include "qnetworkaccessmanager_p.h"
+#include "qnetworkaccesscache_p.h"
+#include "qabstractnetworkcache.h"
+#include "qnetworkrequest.h"
+#include "qnetworkreply.h"
+#include "qnetworkrequest_p.h"
+#include "qnetworkcookie_p.h"
+#include "QtCore/qdatetime.h"
+#include "QtCore/qelapsedtimer.h"
+#include "QtNetwork/qsslconfiguration.h"
+#include "qhttpthreaddelegate_p.h"
+#include "qthread.h"
+#include "QtCore/qcoreapplication.h"
+
+#include "qnetworkcookiejar.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());
+ }
+ }
+}
+
+QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manager,
+ const QNetworkRequest& request,
+ QNetworkAccessManager::Operation& operation,
+ QIODevice* outgoingData)
+ : QNetworkReply(*new QNetworkReplyHttpImplPrivate, manager)
+{
+ Q_D(QNetworkReplyHttpImpl);
+ d->manager = manager;
+ d->managerPrivate = manager->d_func();
+ d->request = request;
+ d->operation = operation;
+ d->outgoingData = outgoingData;
+ d->url = request.url();
+#ifndef QT_NO_OPENSSL
+ d->sslConfiguration = request.sslConfiguration();
+#endif
+
+ // FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache?
+ QIODevice::open(QIODevice::ReadOnly);
+
+
+ // Internal code that does a HTTP reply for the synchronous Ajax
+ // in QtWebKit.
+ QVariant synchronousHttpAttribute = request.attribute(
+ static_cast<QNetworkRequest::Attribute>(QNetworkRequest::SynchronousRequestAttribute));
+ if (synchronousHttpAttribute.isValid()) {
+ d->synchronous = synchronousHttpAttribute.toBool();
+ if (d->synchronous && outgoingData) {
+ // 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.
+ d->outgoingDataBuffer = QSharedPointer<QRingBuffer>(new QRingBuffer());
+ qint64 previousDataSize = 0;
+ do {
+ previousDataSize = d->outgoingDataBuffer->size();
+ d->outgoingDataBuffer->append(d->outgoingData->readAll());
+ } while (d->outgoingDataBuffer->size() != previousDataSize);
+ d->_q_startOperation();
+ return;
+ }
+ }
+
+
+ if (outgoingData) {
+ // there is data to be uploaded, e.g. HTTP POST.
+
+ if (!d->outgoingData->isSequential()) {
+ // fixed size non-sequential (random-access)
+ // just start the operation
+ QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
+ // FIXME make direct call?
+ } else {
+ bool bufferingDisallowed =
+ request.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 (request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
+ QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
+ // FIXME make direct call?
+ } else {
+ d->state = d->Buffering;
+ QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
+ }
+ } else {
+ // _q_startOperation will be called when the buffering has finished.
+ d->state = d->Buffering;
+ QMetaObject::invokeMethod(this, "_q_bufferOutgoingData", Qt::QueuedConnection);
+ }
+ }
+ } else {
+ // No outgoing data (POST, ..)
+ d->_q_startOperation();
+ }
+}
+
+QNetworkReplyHttpImpl::~QNetworkReplyHttpImpl()
+{
+ // Most work is done in private destructor
+}
+
+void QNetworkReplyHttpImpl::close()
+{
+ Q_D(QNetworkReplyHttpImpl);
+
+ if (d->state == QNetworkReplyHttpImplPrivate::Aborted ||
+ d->state == QNetworkReplyHttpImplPrivate::Finished)
+ return;
+
+ // According to the documentation close only stops the download
+ // by closing we can ignore the download part and continue uploading.
+ QNetworkReply::close();
+
+ // call finished which will emit signals
+ // FIXME shouldn't this be emitted Queued?
+ d->error(OperationCanceledError, tr("Operation canceled"));
+ d->finished();
+}
+
+void QNetworkReplyHttpImpl::abort()
+{
+ Q_D(QNetworkReplyHttpImpl);
+ // FIXME
+ if (d->state == QNetworkReplyHttpImplPrivate::Finished || d->state == QNetworkReplyHttpImplPrivate::Aborted)
+ return;
+
+ QNetworkReply::close();
+
+ if (d->state != QNetworkReplyHttpImplPrivate::Finished) {
+ // call finished which will emit signals
+ // FIXME shouldn't this be emitted Queued?
+ d->error(OperationCanceledError, tr("Operation canceled"));
+ d->finished();
+ }
+
+ d->state = QNetworkReplyHttpImplPrivate::Aborted;
+
+ emit abortHttpRequest();
+}
+
+qint64 QNetworkReplyHttpImpl::bytesAvailable() const
+{
+ Q_D(const QNetworkReplyHttpImpl);
+
+ // if we load from cache device
+ if (d->cacheLoadDevice) {
+ return QNetworkReply::bytesAvailable() + d->cacheLoadDevice->bytesAvailable() + d->downloadMultiBuffer.byteAmount();
+ }
+
+ // zerocopy buffer
+ if (d->downloadZerocopyBuffer) {
+ return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition;
+ }
+
+ // normal buffer
+ return QNetworkReply::bytesAvailable() + d->downloadMultiBuffer.byteAmount();
+}
+
+bool QNetworkReplyHttpImpl::isSequential () const
+{
+ // FIXME In the cache of a cached load or the zero-copy buffer we could actually be non-sequential.
+ // FIXME however this requires us to implement stuff like seek() too.
+ return true;
+}
+
+qint64 QNetworkReplyHttpImpl::size() const
+{
+ // FIXME At some point, this could return a proper value, e.g. if we're non-sequential.
+ return QNetworkReply::size();
+}
+
+qint64 QNetworkReplyHttpImpl::readData(char* data, qint64 maxlen)
+{
+ Q_D(QNetworkReplyHttpImpl);
+
+ // cacheload device
+ if (d->cacheLoadDevice) {
+ // FIXME bytesdownloaded, position etc?
+
+ // There is something already in the buffer we buffered before because the user did not read()
+ // anything, so we read there first:
+ if (!d->downloadMultiBuffer.isEmpty()) {
+ return d->downloadMultiBuffer.read(data, maxlen);
+ }
+
+ qint64 ret = d->cacheLoadDevice->read(data, maxlen);
+ return ret;
+ }
+
+ // zerocopy buffer
+ if (d->downloadZerocopyBuffer) {
+ // FIXME bytesdownloaded, position etc?
+
+ qint64 howMuch = qMin(maxlen, (d->downloadBufferCurrentSize - d->downloadBufferReadPosition));
+ memcpy(data, d->downloadZerocopyBuffer + d->downloadBufferReadPosition, howMuch);
+ d->downloadBufferReadPosition += howMuch;
+ return howMuch;
+
+ }
+
+ // normal buffer
+ if (d->downloadMultiBuffer.isEmpty()) {
+ if (d->state == d->Finished || d->state == d->Aborted)
+ return -1;
+ return 0;
+ }
+
+ if (maxlen == 1) {
+ // optimization for getChar()
+ *data = d->downloadMultiBuffer.getChar();
+ return 1;
+ }
+
+ maxlen = qMin<qint64>(maxlen, d->downloadMultiBuffer.byteAmount());
+ return d->downloadMultiBuffer.read(data, maxlen);
+}
+
+void QNetworkReplyHttpImpl::setReadBufferSize(qint64 size)
+{
+ // FIXME, unsupported right now
+ return;
+}
+
+bool QNetworkReplyHttpImpl::canReadLine () const
+{
+ Q_D(const QNetworkReplyHttpImpl);
+
+ if (QNetworkReply::canReadLine())
+ return true;
+
+ if (d->cacheLoadDevice)
+ return d->cacheLoadDevice->canReadLine() || d->downloadMultiBuffer.canReadLine();
+
+ // FIXME zerocopy buffer?
+
+ return d->downloadMultiBuffer.canReadLine();
+}
+
+#ifndef QT_NO_OPENSSL
+void QNetworkReplyHttpImpl::ignoreSslErrors()
+{
+ Q_D(QNetworkReplyHttpImpl);
+
+ d->pendingIgnoreAllSslErrors = true;
+}
+
+void QNetworkReplyHttpImpl::ignoreSslErrorsImplementation(const QList<QSslError> &errors)
+{
+ Q_D(QNetworkReplyHttpImpl);
+
+ // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
+ // is called before QNetworkAccessManager::get() (or post(), etc.)
+ d->pendingIgnoreSslErrorsList = errors;
+}
+
+void QNetworkReplyHttpImpl::setSslConfigurationImplementation(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);
+}
+
+QSslConfiguration QNetworkReplyHttpImpl::sslConfigurationImplementation() const
+{
+ Q_D(const QNetworkReplyHttpImpl);
+ return d->sslConfiguration;
+}
+#endif
+
+QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
+ : QNetworkReplyPrivate()
+ , manager(0)
+ , managerPrivate(0)
+ , synchronous(false)
+ , state(Idle)
+ , statusCode(0)
+ , outgoingData(0)
+ , bytesUploaded(-1)
+ , cacheLoadDevice(0)
+ , loadingFromCache(false)
+ , cacheSaveDevice(0)
+ , cacheEnabled(false)
+ , resumeOffset(0)
+ , preMigrationDownloaded(-1)
+ , bytesDownloaded(0)
+ , lastBytesDownloaded(-1)
+ , downloadBufferReadPosition(0)
+ , downloadBufferCurrentSize(0)
+ , downloadBufferMaximumSize(0)
+ , downloadZerocopyBuffer(0)
+ , pendingDownloadDataEmissions(new QAtomicInt())
+ , pendingDownloadProgressEmissions(new QAtomicInt())
+ #ifndef QT_NO_OPENSSL
+ , pendingIgnoreAllSslErrors(false)
+ #endif
+
+{
+}
+
+QNetworkReplyHttpImplPrivate::~QNetworkReplyHttpImplPrivate()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ // This will do nothing if the request was already finished or aborted
+ emit q->abortHttpRequest();
+}
+
+/*
+ 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 QNetworkReplyHttpImplPrivate::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 = managerPrivate->networkCache;
+ if (!nc)
+ return false; // no local cache
+
+ QNetworkCacheMetaData metaData = nc->metaData(request.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));
+
+ 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);
+}
+
+QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::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 QNetworkReplyHttpImplPrivate::postRequest()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ QThread *thread = 0;
+ if (synchronous) {
+ // A synchronous HTTP request uses its own thread
+ thread = new QThread();
+ QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
+ thread->start();
+ } else if (!managerPrivate->httpThread) {
+ // We use the manager-global thread.
+ // At some point we could switch to having multiple threads if it makes sense.
+ managerPrivate->httpThread = new QThread();
+ QObject::connect(managerPrivate->httpThread, SIGNAL(finished()), managerPrivate->httpThread, SLOT(deleteLater()));
+ managerPrivate->httpThread->start();
+
+ thread = managerPrivate->httpThread;
+ } else {
+ // Asynchronous request, thread already exists
+ thread = managerPrivate->httpThread;
+ }
+
+ QUrl url = request.url();
+ httpRequest.setUrl(url);
+
+ bool ssl = url.scheme().toLower() == QLatin1String("https");
+ q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
+ httpRequest.setSsl(ssl);
+
+
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy transparentProxy, cacheProxy;
+
+ // FIXME the proxy stuff should be done in the HTTP thread
+ foreach (const QNetworkProxy &p, managerPrivate->queryProxy(QNetworkProxyQuery(request.url()))) {
+ //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(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
+ Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
+ Q_ARG(QString, q->tr("No suitable proxy found")));
+ QMetaObject::invokeMethod(q, "_q_finished", synchronous ? 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) {
+ 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;
+
+ // 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
+ QObject::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 = synchronous;
+
+ // The authentication manager is used to avoid the BlockingQueuedConnection communication
+ // from HTTP thread to user thread in some cases.
+ delegate->authenticationManager = managerPrivate->authenticationManager;
+
+ if (!synchronous) {
+ // 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
+ QObject::connect(delegate, SIGNAL(downloadData(QByteArray)),
+ q, SLOT(replyDownloadData(QByteArray)),
+ Qt::QueuedConnection);
+ QObject::connect(delegate, SIGNAL(downloadFinished()),
+ q, SLOT(replyFinished()),
+ Qt::QueuedConnection);
+ QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
+ q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
+ Qt::QueuedConnection);
+ QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
+ q, SLOT(replyDownloadProgressSlot(qint64,qint64)),
+ Qt::QueuedConnection);
+ QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
+ q, SLOT(httpError(QNetworkReply::NetworkError, const QString)),
+ Qt::QueuedConnection);
+#ifndef QT_NO_OPENSSL
+ QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
+ q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
+ Qt::QueuedConnection);
+#endif
+ // Those need to report back, therefire BlockingQueuedConnection
+ QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
+ Qt::BlockingQueuedConnection);
+#ifndef QT_NO_NETWORKPROXY
+ QObject::connect(delegate, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ q, SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
+ Qt::BlockingQueuedConnection);
+#endif
+#ifndef QT_NO_OPENSSL
+ QObject::connect(delegate, SIGNAL(sslErrors(QList<QSslError>,bool*,QList<QSslError>*)),
+ q, SLOT(replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *)),
+ Qt::BlockingQueuedConnection);
+#endif
+ // This signal we will use to start the request.
+ QObject::connect(q, SIGNAL(startHttpRequest()), delegate, SLOT(startRequest()));
+ QObject::connect(q, 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(q, 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)),
+ q, SLOT(wantUploadDataSlot(qint64)));
+ QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
+ q, SLOT(sentUploadDataSlot(qint64)));
+ QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
+ q, SLOT(resetUploadDataSlot(bool*)),
+ Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
+ }
+ } else if (synchronous) {
+ QObject::connect(q, 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 start() 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 (synchronous) {
+ emit q->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 q->startHttpRequest(); // Signal to the HTTP thread and go back to user.
+ }
+}
+
+void QNetworkReplyHttpImplPrivate::invalidateCache()
+{
+ QAbstractNetworkCache *nc = managerPrivate->networkCache;
+ if (nc)
+ nc->remove(request.url());
+}
+
+void QNetworkReplyHttpImplPrivate::initCacheSaveDevice()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // 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 = 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 = managerPrivate->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",
+ managerPrivate->networkCache->metaObject()->className());
+
+ managerPrivate->networkCache->remove(url);
+ cacheSaveDevice = 0;
+ cacheEnabled = false;
+ }
+}
+
+void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // If we're closed just ignore this data
+ if (!q->isOpen())
+ return;
+
+ 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();
+
+ if (cacheEnabled && !cacheSaveDevice) {
+ initCacheSaveDevice();
+ }
+
+ qint64 bytesWritten = 0;
+ for (int i = 0; i < pendingDownloadDataCopy.bufferCount(); i++) {
+ QByteArray const &item = pendingDownloadDataCopy[i];
+
+ if (cacheSaveDevice)
+ cacheSaveDevice->write(item.constData(), item.size());
+ downloadMultiBuffer.append(item);
+
+ bytesWritten += item.size();
+ }
+ pendingDownloadDataCopy.clear();
+
+ bytesDownloaded += bytesWritten;
+ lastBytesDownloaded = bytesDownloaded;
+
+
+ QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ if (preMigrationDownloaded != Q_INT64_C(-1))
+ totalSize = totalSize.toLongLong() + preMigrationDownloaded;
+
+ 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());
+
+}
+
+void QNetworkReplyHttpImplPrivate::replyFinished()
+{
+ // We are already loading from cache, we still however
+ // got this signal because it was posted already
+ if (loadingFromCache)
+ return;
+
+ finished();
+}
+
+void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ 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 = q->rawHeader("location");
+ QUrl url = QUrl::fromEncoded(header);
+ if (!url.isValid())
+ url = QUrl(QLatin1String(header));
+ // FIXME?
+ //redirectionRequested(url);
+ q->setAttribute(QNetworkRequest::RedirectionTargetAttribute, url);
+ }
+}
+
+void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
+ (QList<QPair<QByteArray,QByteArray> > hm,
+ int sc,QString rp,bool pu,
+ QSharedPointer<char> db,
+ qint64 contentLength)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ statusCode = sc;
+ reasonPhrase = rp;
+
+ // Download buffer
+ if (!db.isNull()) {
+ //setDownloadBuffer(db, contentLength);
+ downloadBufferPointer = db;
+ downloadZerocopyBuffer = downloadBufferPointer.data();
+ downloadBufferCurrentSize = 0;
+ downloadBufferMaximumSize = contentLength;
+ q->setAttribute(QNetworkRequest::DownloadBufferAttribute, qVariantFromValue<QSharedPointer<char> > (downloadBufferPointer));
+ }
+
+ q->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 = q->rawHeader(it->first);
+ if (!value.isEmpty()) {
+ if (qstricmp(it->first.constData(), "set-cookie") == 0)
+ value += '\n';
+ else
+ value += ", ";
+ }
+ value += it->second;
+ q->setRawHeader(it->first, value);
+ }
+
+ q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
+ q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
+
+ // is it a redirection?
+ checkForRedirect(statusCode);
+
+ if (statusCode >= 500 && statusCode < 600) {
+ QAbstractNetworkCache *nc = managerPrivate->networkCache;
+ if (nc) {
+ QNetworkCacheMetaData metaData = nc->metaData(request.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 = managerPrivate->networkCache;
+ if (nc) {
+ QNetworkCacheMetaData oldMetaData = nc->metaData(request.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 QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceived, qint64 bytesTotal)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // If we're closed just ignore this data
+ if (!q->isOpen())
+ return;
+
+ // 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;
+ }
+
+ if (!q->isOpen())
+ return;
+
+ if (cacheEnabled && bytesReceived == bytesTotal) {
+ // Write everything in one go if we use a download buffer. might be more performant.
+ initCacheSaveDevice();
+ // need to check again if cache enabled and device exists
+ if (cacheSaveDevice && cacheEnabled)
+ cacheSaveDevice->write(downloadZerocopyBuffer, bytesTotal);
+ // FIXME where is it closed?
+ }
+
+ 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 QNetworkReplyHttpImplPrivate::httpAuthenticationRequired(const QHttpNetworkRequest &,
+ QAuthenticator *auth)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ managerPrivate->authenticationRequired(auth, q_func(), synchronous, url, &urlForLastAuthentication);
+}
+
+#ifndef QT_NO_NETWORKPROXY
+void QNetworkReplyHttpImplPrivate::proxyAuthenticationRequired(const QNetworkProxy &proxy,
+ QAuthenticator *authenticator)
+{
+ managerPrivate->proxyAuthenticationRequired(proxy, synchronous, authenticator, &lastProxyAuthentication);
+}
+#endif
+
+void QNetworkReplyHttpImplPrivate::httpError(QNetworkReply::NetworkError errorCode,
+ const QString &errorString)
+{
+#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
+ qDebug() << "http error!" << errorCode << errorString;
+#endif
+
+ // FIXME?
+ error(errorCode, errorString);
+}
+
+#ifndef QT_NO_OPENSSL
+void QNetworkReplyHttpImplPrivate::replySslErrors(
+ const QList<QSslError> &list, bool *ignoreAll, QList<QSslError> *toBeIgnored)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ emit q->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 QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfiguration &sslConfiguration)
+{
+ // Receiving the used SSL configuration from the HTTP thread
+ this->sslConfiguration = sslConfiguration;
+}
+#endif
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
+{
+ *r = uploadByteDevice->reset();
+}
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 amount)
+{
+ uploadByteDevice->advanceReadPointer(amount);
+}
+
+// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
+void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // 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 q->haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
+}
+
+/*
+ A simple web page that can be used to test us: http://www.procata.com/cachetest/
+ */
+bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData &metaData)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ setCachingEnabled(false);
+ if (!metaData.isValid())
+ return false;
+
+ QAbstractNetworkCache *nc = managerPrivate->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(q);
+
+ QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
+ int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ if (status < 100)
+ status = 200; // fake it
+
+ q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
+ q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
+ q->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);
+
+ cacheLoadDevice = contents;
+ q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
+ q->connect(cacheLoadDevice, SIGNAL(readChannelFinished()), SLOT(_q_cacheLoadReadyRead()));
+
+ // 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(q, "metaDataChanged", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(q, "_q_cacheLoadReadyRead", Qt::QueuedConnection);
+
+
+#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;
+}
+
+QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
+{
+ Q_Q(const QNetworkReplyHttpImpl);
+
+ QNetworkCacheMetaData metaData = oldMetaData;
+
+ QNetworkHeadersPrivate cacheHeaders;
+ cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
+ QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
+
+ QList<QByteArray> newHeaders = q->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;
+
+ // for 4.6.0, we were planning to not store the date header in the
+ // cached resource; through that we planned to reduce the number
+ // of writes to disk when using a QNetworkDiskCache (i.e. don't
+ // write to disk when only the date changes).
+ // However, without the date we cannot calculate the age of the page
+ // anymore.
+ //if (header == "date")
+ //continue;
+
+ // Don't store Warning 1xx headers
+ if (header == "warning") {
+ QByteArray v = q->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, q->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 QNetworkReplyHttpImplPrivate::canResume() const
+{
+ Q_Q(const QNetworkReplyHttpImpl);
+
+ // 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 (!q->hasRawHeader(acceptRangesheaderName) || q->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 (downloadZerocopyBuffer)
+ return false;
+
+ return true;
+}
+
+void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset)
+{
+ resumeOffset = offset;
+}
+
+/*!
+ 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 QNetworkReplyHttpImplPrivate::start()
+{
+ if (!managerPrivate->networkSession) {
+ postRequest();
+ return true;
+ }
+
+ // This is not ideal.
+ const QString host = url.host();
+ if (host == QLatin1String("localhost") ||
+ QHostAddress(host) == QHostAddress::LocalHost ||
+ QHostAddress(host) == QHostAddress::LocalHostIPv6) {
+ // Don't need an open session for localhost access.
+ postRequest();
+ return true;
+ }
+
+ if (managerPrivate->networkSession->isOpen() &&
+ managerPrivate->networkSession->state() == QNetworkSession::Connected) {
+ postRequest();
+ return true;
+ }
+
+ return false;
+}
+
+void QNetworkReplyHttpImplPrivate::_q_startOperation()
+{
+ // ensure this function is only being called once
+ if (state == Working) {
+ qDebug("QNetworkReplyImpl::_q_startOperation was called more than once");
+ return;
+ }
+ state = Working;
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ if (!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 = managerPrivate->networkSession.data();
+
+ if (session) {
+ Q_Q(QNetworkReplyHttpImpl);
+
+ 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 (synchronous) {
+ state = Finished;
+ q_func()->setFinished(true);
+ } else {
+ if (state != Finished) {
+// if (operation == QNetworkAccessManager::GetOperation)
+// pendingNotifications.append(NotifyDownstreamReadyWrite);
+
+// handleNotifications();
+
+ }
+ }
+}
+
+void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ if (state != Working)
+ return;
+ if (!cacheLoadDevice || !q->isOpen() || !cacheLoadDevice->bytesAvailable())
+ return;
+
+ // FIXME Optimize to use zerocopy download buffer if it is a QBuffer.
+ // Needs to be done where sendCacheContents() (?) of HTTP is emitting
+ // metaDataChanged ?
+
+
+ // FIXME
+ lastBytesDownloaded = bytesDownloaded;
+ QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+
+ //pauseNotificationHandling();
+ // emit readyRead before downloadProgress incase this will cause events to be
+ // processed and we get into a recursive call (as in QProgressDialog).
+
+ // This readyRead() goes to the user. The user then may or may not read() anything.
+ emit q->readyRead();
+ emit q->downloadProgress(bytesDownloaded,
+ totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+
+ // If there are still bytes available in the cacheLoadDevice then the user did not read
+ // in response to the readyRead() signal. This means we have to load from the cacheLoadDevice
+ // and buffer that stuff. This is needed to be able to properly emit finished() later.
+ while (cacheLoadDevice->bytesAvailable()) {
+ downloadMultiBuffer.append(cacheLoadDevice->readAll());
+ }
+
+ if (cacheLoadDevice->isSequential()) {
+ // check if end and we can read the EOF -1
+ char c;
+ qint64 actualCount = cacheLoadDevice->read(&c, 1);
+ if (actualCount < 0) {
+ cacheLoadDevice->deleteLater();
+ cacheLoadDevice = 0;
+ QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
+ } else if (actualCount == 1) {
+ // This is most probably not happening since most QIODevice returned something proper for bytesAvailable()
+ // and had already been "emptied".
+ cacheLoadDevice->ungetChar(c);
+ }
+ } else if ((!cacheLoadDevice->isSequential() && cacheLoadDevice->atEnd())) {
+ // This codepath is in case the cache device is a QBuffer, e.g. from QNetworkDiskCache.
+ cacheLoadDevice->deleteLater();
+ cacheLoadDevice = 0;
+ QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
+ }
+
+}
+
+
+void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingDataFinished()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // 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 QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ 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 QNetworkReplyHttpImplPrivate::_q_networkSessionConnected()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ if (!manager)
+ return;
+
+ QNetworkSession *session = managerPrivate->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 QNetworkReplyHttpImplPrivate::_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
+
+
+// need to have this function since the reply is a private member variable
+// and the special backends need to access this.
+void QNetworkReplyHttpImplPrivate::emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ if (isFinished)
+ return;
+ emit q->uploadProgress(bytesSent, bytesTotal);
+}
+
+QNonContiguousByteDevice* QNetworkReplyHttpImplPrivate::createUploadByteDevice()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ if (outgoingDataBuffer)
+ uploadByteDevice = QSharedPointer<QNonContiguousByteDevice>(QNonContiguousByteDeviceFactory::create(outgoingDataBuffer));
+ else if (outgoingData) {
+ uploadByteDevice = QSharedPointer<QNonContiguousByteDevice>(QNonContiguousByteDeviceFactory::create(outgoingData));
+ } else {
+ return 0;
+ }
+
+ bool bufferDisallowed =
+ request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute,
+ QVariant(false)) == QVariant(true);
+ if (bufferDisallowed)
+ uploadByteDevice->disableReset();
+
+ // We want signal emissions only for normal asynchronous uploads
+ if (!synchronous)
+ QObject::connect(uploadByteDevice.data(), SIGNAL(readProgress(qint64,qint64)),
+ q, SLOT(emitReplyUploadProgress(qint64,qint64)));
+
+ return uploadByteDevice.data();
+}
+
+void QNetworkReplyHttpImplPrivate::_q_finished()
+{
+ // This gets called queued, just forward to real call then
+ finished();
+}
+
+void QNetworkReplyHttpImplPrivate::finished()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ 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;
+
+ // FIXME why should it be 0
+ if (manager) {
+#ifndef QT_NO_BEARERMANAGEMENT
+ QNetworkSession *session = managerPrivate->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 QNetworkReplyHttpImplPrivate::_q_error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
+{
+ this->error(code, errorMessage);
+}
+
+
+void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, const QString &errorMessage)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+ // 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 QNetworkReplyHttpImplPrivate::metaDataChanged()
+{
+ // FIXME merge this with replyDownloadMetaData(); ?
+
+ Q_Q(QNetworkReplyHttpImpl);
+ // 1. do we have cookies?
+ // 2. are we allowed to set them?
+ if (cookedHeaders.contains(QNetworkRequest::SetCookieHeader) && manager
+ && (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();
+}
+
+/*
+ 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 QNetworkReplyHttpImplPrivate::migrateBackend()
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ // 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 (!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 (cacheLoadDevice)
+ return true;
+
+ state = 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);
+// }
+
+ // FIXME
+ Q_ASSERT(0);
+ // What probably needs to be done is an abort and then re-send?
+
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
+
+ return true;
+}
+
+
+void QNetworkReplyHttpImplPrivate::createCache()
+{
+ // check if we can save and if we're allowed to
+ if (!managerPrivate->networkCache
+ || !request.attribute(QNetworkRequest::CacheSaveControlAttribute, true).toBool()
+ || request.attribute(QNetworkRequest::CacheLoadControlAttribute,
+ QNetworkRequest::PreferNetwork).toInt()
+ == QNetworkRequest::AlwaysNetwork)
+ return;
+ cacheEnabled = true;
+}
+
+bool QNetworkReplyHttpImplPrivate::isCachingEnabled() const
+{
+ return (cacheEnabled && managerPrivate->networkCache != 0);
+}
+
+void QNetworkReplyHttpImplPrivate::setCachingEnabled(bool enable)
+{
+ if (!enable && !cacheEnabled)
+ return; // nothing to do
+ if (enable && cacheEnabled)
+ return; // nothing to do either!
+
+ if (enable) {
+ if (bytesDownloaded) {
+ qDebug() << "x" << 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)");
+ managerPrivate->networkCache->remove(url);
+ cacheSaveDevice = 0;
+ cacheEnabled = false;
+ }
+}
+
+void QNetworkReplyHttpImplPrivate::completeCacheSave()
+{
+ if (cacheEnabled && errorCode != QNetworkReplyImpl::NoError) {
+ managerPrivate->networkCache->remove(url);
+ } else if (cacheEnabled && cacheSaveDevice) {
+ managerPrivate->networkCache->insert(cacheSaveDevice);
+ }
+ cacheSaveDevice = 0;
+ cacheEnabled = false;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
new file mode 100644
index 0000000000..0420ffd313
--- /dev/null
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -0,0 +1,309 @@
+/****************************************************************************
+**
+** 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 QNETWORKREPLYHTTPIMPL_P_H
+#define QNETWORKREPLYHTTPIMPL_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 "qnetworkreply.h"
+
+#include "QtCore/qpointer.h"
+#include "QtCore/qdatetime.h"
+#include "QtCore/qsharedpointer.h"
+#include "qatomic.h"
+
+#include <QtNetwork/QNetworkCacheMetaData>
+#include <private/qhttpnetworkrequest_p.h>
+#include <private/qbytedata_p.h>
+#include <private/qnetworkreply_p.h>
+#include <QtNetwork/QNetworkProxy>
+
+#ifndef QT_NO_OPENSSL
+#include <QtNetwork/QSslConfiguration>
+#endif
+
+#ifndef QT_NO_HTTP
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+
+class QNetworkReplyHttpImplPrivate;
+class QNetworkReplyHttpImpl: public QNetworkReply
+{
+ Q_OBJECT
+public:
+ QNetworkReplyHttpImpl(QNetworkAccessManager* const, const QNetworkRequest&, QNetworkAccessManager::Operation&, QIODevice* outgoingData);
+ virtual ~QNetworkReplyHttpImpl();
+
+ void close();
+ void abort();
+ qint64 bytesAvailable() const;
+ bool isSequential () const;
+ qint64 size() const;
+ qint64 readData(char*, qint64);
+ void setReadBufferSize(qint64 size);
+ bool canReadLine () const;
+
+#ifndef QT_NO_OPENSSL
+ void ignoreSslErrors();
+ // ### Qt5 Add proper virtual
+ Q_INVOKABLE void ignoreSslErrorsImplementation(const QList<QSslError> &errors);
+ // ### Qt5 Add proper virtual
+ Q_INVOKABLE void setSslConfigurationImplementation(const QSslConfiguration &configuration);
+ // ### Qt5 Add proper virtual
+ Q_INVOKABLE QSslConfiguration sslConfigurationImplementation() const;
+#endif
+
+ Q_DECLARE_PRIVATE(QNetworkReplyHttpImpl)
+ Q_PRIVATE_SLOT(d_func(), void _q_startOperation())
+ Q_PRIVATE_SLOT(d_func(), void _q_cacheLoadReadyRead())
+ 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
+ Q_PRIVATE_SLOT(d_func(), void _q_finished())
+ Q_PRIVATE_SLOT(d_func(), void _q_error(QNetworkReply::NetworkError, const QString &))
+
+ // From reply
+ Q_PRIVATE_SLOT(d_func(), void replyDownloadData(QByteArray))
+ Q_PRIVATE_SLOT(d_func(), void replyFinished())
+ Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64))
+ Q_PRIVATE_SLOT(d_func(), void replyDownloadProgressSlot(qint64,qint64))
+ Q_PRIVATE_SLOT(d_func(), void httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *))
+ Q_PRIVATE_SLOT(d_func(), void httpError(QNetworkReply::NetworkError, const QString &))
+#ifndef QT_NO_OPENSSL
+ Q_PRIVATE_SLOT(d_func(), void replySslErrors(const QList<QSslError> &, bool *, QList<QSslError> *))
+ Q_PRIVATE_SLOT(d_func(), void replySslConfigurationChanged(const QSslConfiguration&))
+#endif
+ Q_PRIVATE_SLOT(d_func(), void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth))
+
+ Q_PRIVATE_SLOT(d_func(), void resetUploadDataSlot(bool *r))
+ Q_PRIVATE_SLOT(d_func(), void wantUploadDataSlot(qint64))
+ Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64))
+ Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64))
+
+
+
+signals:
+ // To HTTP thread:
+ void startHttpRequest();
+ void abortHttpRequest();
+
+ void startHttpRequestSynchronously();
+
+ void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
+};
+
+class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
+{
+public:
+
+ static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio);
+
+ 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.
+ };
+
+ QNetworkReplyHttpImplPrivate();
+ ~QNetworkReplyHttpImplPrivate();
+
+ bool start();
+ void _q_startOperation();
+
+ void _q_cacheLoadReadyRead();
+
+ void _q_bufferOutgoingData();
+ void _q_bufferOutgoingDataFinished();
+
+#ifndef QT_NO_BEARERMANAGEMENT
+ void _q_networkSessionConnected();
+ void _q_networkSessionFailed();
+#endif
+ void _q_finished();
+
+
+ // FIXME
+ void finished();
+ void error(QNetworkReply::NetworkError code, const QString &errorString);
+ void _q_error(QNetworkReply::NetworkError code, const QString &errorString);
+ void metaDataChanged();
+
+ void redirectionRequested(const QUrl &target);
+ void checkForRedirect(const int statusCode);
+
+
+ // incoming from user
+ QNetworkAccessManager *manager;
+ QNetworkAccessManagerPrivate *managerPrivate;
+ QNetworkRequest request;
+ QNetworkAccessManager::Operation operation; // FIXME already in replyprivate?
+ QHttpNetworkRequest httpRequest; // There is also a copy in the HTTP thread
+ bool synchronous;
+
+ State state;
+
+ // from http thread
+ int statusCode;
+ QString reasonPhrase;
+
+ // upload
+ QNonContiguousByteDevice* createUploadByteDevice();
+ QSharedPointer<QNonContiguousByteDevice> uploadByteDevice;
+ QIODevice *outgoingData;
+ QSharedPointer<QRingBuffer> outgoingDataBuffer;
+ void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); // dup?
+ qint64 bytesUploaded;
+
+
+ // cache
+ void createCache();
+ void completeCacheSave();
+ void setCachingEnabled(bool enable);
+ bool isCachingEnabled() const;
+ void initCacheSaveDevice();
+ QAbstractNetworkCache *networkCache() const;
+ QIODevice *cacheLoadDevice;
+ bool loadingFromCache;
+
+ QIODevice *cacheSaveDevice;
+ bool cacheEnabled; // is this for saving?
+
+
+ QUrl urlForLastAuthentication;
+#ifndef QT_NO_NETWORKPROXY
+ QNetworkProxy lastProxyAuthentication;
+ QList<QNetworkProxy> proxyList;
+#endif
+
+
+ bool migrateBackend();
+ bool canResume() const;
+ void setResumeOffset(quint64 offset);
+ quint64 resumeOffset;
+ qint64 preMigrationDownloaded;
+
+ // Used for normal downloading. For "zero copy" the downloadZerocopyBuffer is used
+ QByteDataBuffer downloadMultiBuffer;
+ QByteDataBuffer pendingDownloadData; // For signal compression
+ qint64 bytesDownloaded;
+ qint64 lastBytesDownloaded;
+ void setDownloadBuffer(QSharedPointer<char> sp, qint64 size);
+ char* getDownloadBuffer(qint64 size);
+
+ // only used when the "zero copy" style is used. Else downloadMultiBuffer 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* downloadZerocopyBuffer;
+
+ // Will be increased by HTTP thread:
+ QSharedPointer<QAtomicInt> pendingDownloadDataEmissions;
+ QSharedPointer<QAtomicInt> pendingDownloadProgressEmissions;
+
+
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration sslConfiguration;
+ bool pendingIgnoreAllSslErrors;
+ QList<QSslError> pendingIgnoreSslErrorsList;
+#endif
+
+
+ bool loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest);
+ void invalidateCache();
+ bool sendCacheContents(const QNetworkCacheMetaData &metaData);
+ QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const;
+
+
+ void postRequest();
+
+
+
+public:
+ // 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
+#ifndef QT_NO_NETWORKPROXY
+ void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth);
+#endif
+
+ // From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
+ void resetUploadDataSlot(bool *r);
+ void wantUploadDataSlot(qint64);
+ void sentUploadDataSlot(qint64);
+
+ Q_DECLARE_PUBLIC(QNetworkReplyHttpImpl)
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_HTTP
+
+#endif
diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp
index 9eb505d1b7..e50f3d627d 100644
--- a/src/network/access/qnetworkreplyimpl.cpp
+++ b/src/network/access/qnetworkreplyimpl.cpp
@@ -47,7 +47,6 @@
#include "QtCore/qdatetime.h"
#include "QtNetwork/qsslconfiguration.h"
#include "QtNetwork/qnetworksession.h"
-#include "qnetworkaccesshttpbackend_p.h"
#include "qnetworkaccessmanager_p.h"
#include <QtCore/QCoreApplication>
@@ -90,10 +89,10 @@ void QNetworkReplyImplPrivate::_q_startOperation()
return;
}
+ if (!backend->start()) {
#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
+ // QNetworkAccessManager will call _q_startOperation again for us when the session
// state changes.
state = WaitingForSession;
@@ -109,11 +108,20 @@ void QNetworkReplyImplPrivate::_q_startOperation()
session->open();
} else {
qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
+ state = Working;
+ error(QNetworkReplyImpl::UnknownNetworkError,
+ QCoreApplication::translate("QNetworkReply", "Network session error."));
+ finished();
}
-
+#else
+ qWarning("Backend start failed");
+ state = Working;
+ error(QNetworkReplyImpl::UnknownNetworkError,
+ QCoreApplication::translate("QNetworkReply", "backend start error."));
+ finished();
+#endif
return;
}
-#endif
if (backend && backend->isSynchronous()) {
state = Finished;
@@ -356,7 +364,7 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const
// 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())) {
+ if (backend && backend->isSynchronous()) {
_q_startOperation();
} else {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
@@ -1043,11 +1051,7 @@ bool QNetworkReplyImplPrivate::migrateBackend()
}
#ifndef QT_NO_HTTP
- if (qobject_cast<QNetworkAccessHttpBackend *>(backend)) {
- _q_startOperation();
- } else {
- QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
- }
+ QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
#endif // QT_NO_HTTP
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 338969a909..a9a1e49572 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -247,10 +247,13 @@ QT_BEGIN_NAMESPACE
\value AlwaysNetwork always load from network and do not
check if the cache has a valid entry (similar to the
- "Reload" feature in browsers)
+ "Reload" feature in browsers); in addition, force intermediate
+ caches to re-validate.
\value PreferNetwork default value; load from the network
- if the cached entry is older than the network entry
+ if the cached entry is older than the network entry. This will never
+ return stale data from the cache, but revalidate resources that
+ have become stale.
\value PreferCache load from cache if available,
otherwise load from network. Note that this can return possibly
diff --git a/src/network/bearer/qnetworkconfigmanager_p.cpp b/src/network/bearer/qnetworkconfigmanager_p.cpp
index e9b6703dd9..a948d917c4 100644
--- a/src/network/bearer/qnetworkconfigmanager_p.cpp
+++ b/src/network/bearer/qnetworkconfigmanager_p.cpp
@@ -385,8 +385,6 @@ void QNetworkConfigurationManagerPrivate::updateConfigurations()
this, SLOT(configurationRemoved(QNetworkConfigurationPrivatePointer)));
connect(engine, SIGNAL(configurationChanged(QNetworkConfigurationPrivatePointer)),
this, SLOT(configurationChanged(QNetworkConfigurationPrivatePointer)));
-
- QMetaObject::invokeMethod(engine, "initialize");
}
}
@@ -410,8 +408,19 @@ void QNetworkConfigurationManagerPrivate::updateConfigurations()
startPolling();
}
- if (firstUpdate)
+ if (firstUpdate) {
firstUpdate = false;
+ QList<QBearerEngine*> enginesToInitialize = sessionEngines; //shallow copy the list in case it is modified when we unlock mutex
+ Qt::ConnectionType connectionType;
+ if (QCoreApplicationPrivate::mainThread() == QThread::currentThread())
+ connectionType = Qt::DirectConnection;
+ else
+ connectionType = Qt::BlockingQueuedConnection;
+ locker.unlock();
+ foreach (QBearerEngine* engine, enginesToInitialize) {
+ QMetaObject::invokeMethod(engine, "initialize", connectionType);
+ }
+ }
}
void QNetworkConfigurationManagerPrivate::performAsyncConfigurationUpdate()
diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp
index a16d4ca451..c86f510914 100644
--- a/src/network/kernel/qhostinfo.cpp
+++ b/src/network/kernel/qhostinfo.cpp
@@ -288,7 +288,7 @@ QHostInfo QHostInfoPrivate::fromName(const QString &name, QSharedPointer<QNetwor
#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)
+QHostInfo QHostInfoAgent::fromName(const QString &hostName, QSharedPointer<QNetworkSession>)
{
return QHostInfoAgent::fromName(hostName);
}
diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp
index 68ff95543a..14db913495 100644
--- a/src/network/kernel/qnetworkproxy.cpp
+++ b/src/network/kernel/qnetworkproxy.cpp
@@ -228,6 +228,10 @@
#include "qmutex.h"
#include "qurl.h"
+#ifndef QT_NO_BEARERMANAGEMENT
+#include <QtNetwork/QNetworkConfiguration>
+#endif
+
QT_BEGIN_NAMESPACE
class QSocks5SocketEngineHandler;
@@ -716,6 +720,9 @@ public:
QUrl remote;
int localPort;
QNetworkProxyQuery::QueryType type;
+#ifndef QT_NO_BEARERMANAGEMENT
+ QNetworkConfiguration config;
+#endif
};
template<> void QSharedDataPointer<QNetworkProxyQueryPrivate>::detach()
@@ -777,6 +784,11 @@ template<> void QSharedDataPointer<QNetworkProxyQueryPrivate>::detach()
like choosing an caching HTTP proxy for HTTP-based connections,
but a more powerful SOCKSv5 proxy for all others.
+ The network configuration specifies which configuration to use,
+ when bearer management is used. For example on a mobile phone
+ the proxy settings are likely to be different for the cellular
+ network vs WLAN.
+
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.
@@ -902,6 +914,68 @@ QNetworkProxyQuery::QNetworkProxyQuery(quint16 bindPort, const QString &protocol
d->type = queryType;
}
+#ifndef QT_NO_BEARERMANAGEMENT
+/*!
+ Constructs a QNetworkProxyQuery with the URL \a requestUrl and
+ sets the query type to \a queryType. The specified \a networkConfiguration
+ is used to resolve the proxy settings.
+
+ \sa protocolTag(), peerHostName(), peerPort(), networkConfiguration()
+*/
+QNetworkProxyQuery::QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ const QUrl &requestUrl, QueryType queryType)
+{
+ d->config = networkConfiguration;
+ 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. The specified \a networkConfiguration
+ is used to resolve the proxy settings.
+
+ \sa networkConfiguration()
+*/
+QNetworkProxyQuery::QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ const QString &hostname, int port,
+ const QString &protocolTag,
+ QueryType queryType)
+{
+ d->config = networkConfiguration;
+ 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. The specified \a networkConfiguration
+ is used to resolve the proxy settings.
+
+ 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(), networkConfiguration()
+*/
+QNetworkProxyQuery::QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ quint16 bindPort, const QString &protocolTag,
+ QueryType queryType)
+{
+ d->config = networkConfiguration;
+ d->remote.setScheme(protocolTag);
+ d->localPort = bindPort;
+ d->type = queryType;
+}
+#endif
+
/*!
Constructs a QNetworkProxyQuery object that is a copy of \a other.
*/
@@ -1116,6 +1190,30 @@ void QNetworkProxyQuery::setUrl(const QUrl &url)
d->remote = url;
}
+#ifndef QT_NO_BEARERMANAGEMENT
+QNetworkConfiguration QNetworkProxyQuery::networkConfiguration() const
+{
+ return d ? d->config : QNetworkConfiguration();
+}
+
+/*!
+ Sets the network configuration component of this QNetworkProxyQuery
+ object to be \a networkConfiguration. The network configuration can
+ be used to return different proxy settings based on the network in
+ use, for example WLAN vs cellular networks on a mobile phone.
+
+ In the case of "user choice" or "service network" configurations,
+ you should first start the QNetworkSession and obtain the active
+ configuration from its properties.
+
+ \sa networkConfiguration
+*/
+void QNetworkProxyQuery::setNetworkConfiguration(const QNetworkConfiguration &networkConfiguration)
+{
+ d->config = networkConfiguration;
+}
+#endif
+
/*!
\class QNetworkProxyFactory
\brief The QNetworkProxyFactory class provides fine-grained proxy selection.
diff --git a/src/network/kernel/qnetworkproxy.h b/src/network/kernel/qnetworkproxy.h
index 26562d533f..e16b29e93a 100644
--- a/src/network/kernel/qnetworkproxy.h
+++ b/src/network/kernel/qnetworkproxy.h
@@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE
QT_MODULE(Network)
class QUrl;
+class QNetworkConfiguration;
class QNetworkProxyQueryPrivate;
class Q_NETWORK_EXPORT QNetworkProxyQuery
@@ -73,6 +74,16 @@ public:
QNetworkProxyQuery(quint16 bindPort, const QString &protocolTag = QString(),
QueryType queryType = TcpServer);
QNetworkProxyQuery(const QNetworkProxyQuery &other);
+#ifndef QT_NO_BEARERMANAGEMENT
+ QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ const QUrl &requestUrl, QueryType queryType = UrlRequest);
+ QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ const QString &hostname, int port, const QString &protocolTag = QString(),
+ QueryType queryType = TcpSocket);
+ QNetworkProxyQuery(const QNetworkConfiguration &networkConfiguration,
+ quint16 bindPort, const QString &protocolTag = QString(),
+ QueryType queryType = TcpServer);
+#endif
~QNetworkProxyQuery();
QNetworkProxyQuery &operator=(const QNetworkProxyQuery &other);
bool operator==(const QNetworkProxyQuery &other) const;
@@ -97,6 +108,11 @@ public:
QUrl url() const;
void setUrl(const QUrl &url);
+#ifndef QT_NO_BEARERMANAGEMENT
+ QNetworkConfiguration networkConfiguration() const;
+ void setNetworkConfiguration(const QNetworkConfiguration &networkConfiguration);
+#endif
+
private:
QSharedDataPointer<QNetworkProxyQueryPrivate> d;
};
diff --git a/src/network/kernel/qnetworkproxy_symbian.cpp b/src/network/kernel/qnetworkproxy_symbian.cpp
index 79dfb27396..4ba14c0465 100644
--- a/src/network/kernel/qnetworkproxy_symbian.cpp
+++ b/src/network/kernel/qnetworkproxy_symbian.cpp
@@ -58,6 +58,7 @@
#include <commsdattypeinfov1_1.h> // CCDIAPRecord, CCDProxiesRecord
#include <commsdattypesv1_1.h> // KCDTIdIAPRecord, KCDTIdProxiesRecord
#include <QtNetwork/QNetworkConfigurationManager>
+#include <QtNetwork/QNetworkConfiguration>
#include <QFlags>
using namespace CommsDat;
@@ -88,7 +89,7 @@ class SymbianProxyQuery
{
public:
static QNetworkConfiguration findCurrentConfiguration(QNetworkConfigurationManager& configurationManager);
- static SymbianIapId getIapId(QNetworkConfigurationManager& configurationManager);
+ static SymbianIapId getIapId(QNetworkConfigurationManager &configurationManager, const QNetworkProxyQuery &query);
static CCDIAPRecord *getIapRecordLC(TUint32 aIAPId, CMDBSession &aDb);
static CMDBRecordSet<CCDProxiesRecord> *prepareQueryLC(TUint32 serviceId, TDesC& serviceType);
static QList<QNetworkProxy> proxyQueryL(TUint32 aIAPId, const QNetworkProxyQuery &query);
@@ -137,11 +138,15 @@ QNetworkConfiguration SymbianProxyQuery::findCurrentConfiguration(QNetworkConfig
return currentConfig;
}
-SymbianIapId SymbianProxyQuery::getIapId(QNetworkConfigurationManager& configurationManager)
+SymbianIapId SymbianProxyQuery::getIapId(QNetworkConfigurationManager& configurationManager, const QNetworkProxyQuery &query)
{
SymbianIapId iapId;
- QNetworkConfiguration currentConfig = findCurrentConfiguration(configurationManager);
+ QNetworkConfiguration currentConfig = query.networkConfiguration();
+ if (!currentConfig.isValid()) {
+ //If config is not specified, then try to find out an active or default one
+ 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
@@ -249,7 +254,7 @@ QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro
SymbianIapId iapId;
TInt error;
QNetworkConfigurationManager manager;
- iapId = SymbianProxyQuery::getIapId(manager);
+ iapId = SymbianProxyQuery::getIapId(manager, query);
if (iapId.isValid()) {
TRAP(error, proxies = SymbianProxyQuery::proxyQueryL(iapId.iapId(), query))
if (error != KErrNone) {
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp
index cfb141398c..d8d263370d 100644
--- a/src/network/socket/qabstractsocket.cpp
+++ b/src/network/socket/qabstractsocket.cpp
@@ -556,6 +556,10 @@ bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtoc
q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported"));
return false;
}
+#ifndef QT_NO_NETWORKPROXY
+ //copy user agent to socket engine (if it has been set)
+ socketEngine->setProperty("_q_user-agent", q->property("_q_user-agent"));
+#endif
if (!socketEngine->initialize(q->socketType(), protocol)) {
#if defined (QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) failed (%s)",
diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp
index 7846056221..a8a11a71d4 100644
--- a/src/network/socket/qhttpsocketengine.cpp
+++ b/src/network/socket/qhttpsocketengine.cpp
@@ -501,7 +501,13 @@ void QHttpSocketEngine::slotSocketConnected()
data += path;
data += " HTTP/1.1\r\n";
data += "Proxy-Connection: keep-alive\r\n"
- "User-Agent: Mozilla/5.0\r\n"
+ "User-Agent: ";
+ QVariant v = property("_q_user-agent");
+ if (v.isValid())
+ data += v.toByteArray();
+ else
+ data += "Mozilla/5.0";
+ data += "\r\n"
"Host: " + peerAddress + "\r\n";
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator);
//qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1);
diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp
index c365635990..88b5aca434 100644
--- a/src/network/socket/qsocks5socketengine.cpp
+++ b/src/network/socket/qsocks5socketengine.cpp
@@ -1540,8 +1540,13 @@ qint64 QSocks5SocketEngine::write(const char *data, qint64 len)
// ### Handle this error.
}
- d->data->controlSocket->write(sealedBuf);
+ qint64 written = d->data->controlSocket->write(sealedBuf);
+ if (written <= 0) {
+ QSOCKS5_Q_DEBUG << "native write returned" << written;
+ return written;
+ }
d->data->controlSocket->waitForBytesWritten(0);
+ //NB: returning len rather than written for the OK case, because the "sealing" may increase the length
return len;
#ifndef QT_NO_UDPSOCKET
} else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) {
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 70d7dd8df1..ab4d7f1d76 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -233,7 +233,7 @@ void QSslConfiguration::setProtocol(QSsl::SslProtocol protocol)
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.
+ VerifyPeer for clients, QueryPeer for servers.
\sa setPeerVerifyMode()
*/
@@ -249,7 +249,7 @@ QSslSocket::PeerVerifyMode QSslConfiguration::peerVerifyMode() const
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.
+ VerifyPeer for clients, QueryPeer for servers.
\sa peerVerifyMode()
*/
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 0dbf4b5196..2695df1aef 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -1736,6 +1736,8 @@ void QSslSocket::connectToHostImplementation(const QString &hostName, quint16 po
}
#ifndef QT_NO_NETWORKPROXY
d->plainSocket->setProxy(proxy());
+ //copy user agent down to the plain socket (if it has been set)
+ d->plainSocket->setProperty("_q_user-agent", property("_q_user-agent"));
#endif
QIODevice::open(openMode);
d->plainSocket->connectToHost(hostName, port, openMode);