summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMandeep Sandhu <mandeepsandhu.chd@gmail.com>2014-03-17 19:21:25 +0530
committerAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-03-09 08:32:14 +0000
commitd815de8c26ccee33713e37a0fec2982755dcfe1f (patch)
tree9bfae5a77f94368c139e5290399a9f7ec6471367
parentf7118e7f1b495fc0a0b8d83dff1a207dfddb09aa (diff)
QNetworkAccessManager: Support HTTP redirection
This commit adds support for following HTTP redirect responses on a per request basis. This behavior is disabled by default. It can be switched on by setting the QNetworkRequest::FollowRedirectAttribute to true. 2 new error codes have been added to QNetworkReply: * TooManyRedirectsError: Set when the number of redirects exceed a given value set by the user (defaults to 50 if not set) * UnsecureRedirectError: Set when we are redirecting from a 'https' to 'http' protocol. Test cases for the following scenarios: * Single HTTP redirect using local test server * Changing max-redirects * Testing all redirect related error scenarios The next commit will extend this feature at a QNAM level. Task-number: QTBUG-8232 Change-Id: If9e28ad12bad08bcdc5bc511b1cd59dc9d8150f0 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@theqtcompany.com>
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp55
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h1
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp14
-rw-r--r--src/network/access/qhttpnetworkreply.cpp30
-rw-r--r--src/network/access/qhttpnetworkreply_p.h10
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp24
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h8
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp3
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h2
-rw-r--r--src/network/access/qnetworkreply.cpp32
-rw-r--r--src/network/access/qnetworkreply.h3
-rw-r--r--src/network/access/qnetworkreply_p.h1
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp193
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h13
-rw-r--r--src/network/access/qnetworkrequest.cpp44
-rw-r--r--src/network/access/qnetworkrequest.h5
-rw-r--r--tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp158
17 files changed, 539 insertions, 57 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 7a848dcfcd..7eaa7535ee 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -506,6 +506,55 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
return false;
}
+bool QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply, QUrl *redirectUrl)
+{
+ if (!reply->request().isFollowRedirects())
+ return false;
+
+ QUrl rUrl;
+ QList<QPair<QByteArray, QByteArray> > fields = reply->header();
+ foreach (const QNetworkReply::RawHeaderPair &header, fields) {
+ if (header.first.toLower() == "location") {
+ rUrl = QUrl::fromEncoded(header.second);
+ break;
+ }
+ }
+
+ // If the location url is invalid/empty, we emit ProtocolUnknownError
+ if (!rUrl.isValid()) {
+ emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
+ return false;
+ }
+
+ // Check if we have exceeded max redirects allowed
+ if (reply->request().redirectCount() <= 0) {
+ emitReplyError(socket, reply, QNetworkReply::TooManyRedirectsError);
+ return false;
+ }
+
+ // Resolve the URL if it's relative
+ if (rUrl.isRelative())
+ rUrl = reply->request().url().resolved(rUrl);
+
+ // Check redirect url protocol
+ QString scheme = rUrl.scheme();
+ if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
+ QString previousUrlScheme = reply->request().url().scheme();
+ // Check if we're doing an unsecure redirect (https -> http)
+ if (previousUrlScheme == QLatin1String("https")
+ && scheme == QLatin1String("http")) {
+ emitReplyError(socket, reply, QNetworkReply::InsecureRedirectError);
+ return false;
+ }
+ } else {
+ emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
+ return false;
+ }
+ redirectUrl->setUrl(QString(rUrl.toEncoded()));
+
+ return true;
+}
+
void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request)
{
Q_ASSERT(socket);
@@ -802,6 +851,12 @@ QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError e
case QNetworkReply::SslHandshakeFailedError:
errorString = QCoreApplication::translate("QHttp", "SSL handshake failed");
break;
+ case QNetworkReply::TooManyRedirectsError:
+ errorString = QCoreApplication::translate("QHttp", "Too many redirects");
+ break;
+ case QNetworkReply::InsecureRedirectError:
+ errorString = QCoreApplication::translate("QHttp", "Insecure redirect");
+ break;
default:
// all other errors are treated as QNetworkReply::UnknownNetworkError
errorString = extraDetail;
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index 115eef581e..858cb7be80 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -250,6 +250,7 @@ public:
void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
+ bool parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply, QUrl *redirectUrl);
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy networkProxy;
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 75888c0062..82e573095b 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -492,6 +492,20 @@ void QHttpNetworkConnectionChannel::handleStatus()
bool resend = false;
switch (statusCode) {
+ case 301:
+ case 302:
+ case 303:
+ case 305:
+ case 307: {
+ // Parse the response headers and get the "location" url
+ QUrl redirectUrl;
+ if (connection->d_func()->parseRedirectResponse(socket, reply, &redirectUrl))
+ reply->setRedirectUrl(redirectUrl);
+
+ if (qobject_cast<QHttpNetworkConnection *>(connection))
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ break;
+ }
case 401: // auth required
case 407: // proxy auth required
if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
index 2063ca6bd0..172d4dab5b 100644
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -78,6 +78,23 @@ void QHttpNetworkReply::setUrl(const QUrl &url)
d->url = url;
}
+QUrl QHttpNetworkReply::redirectUrl() const
+{
+ return d_func()->redirectUrl;
+}
+
+void QHttpNetworkReply::setRedirectUrl(const QUrl &url)
+{
+ Q_D(QHttpNetworkReply);
+ d->redirectUrl = url;
+}
+
+bool QHttpNetworkReply::isHttpRedirect(int statusCode)
+{
+ return (statusCode == 301 || statusCode == 302 || statusCode == 303
+ || statusCode == 305 || statusCode == 307);
+}
+
qint64 QHttpNetworkReply::contentLength() const
{
return d_func()->contentLength();
@@ -267,6 +284,11 @@ void QHttpNetworkReply::setSpdyWasUsed(bool spdy)
d_func()->spdyUsed = spdy;
}
+bool QHttpNetworkReply::isRedirecting() const
+{
+ return d_func()->isRedirecting();
+}
+
QHttpNetworkConnection* QHttpNetworkReply::connection()
{
return d_func()->connection;
@@ -910,6 +932,14 @@ qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *c
return bytes;
}
+bool QHttpNetworkReplyPrivate::isRedirecting() const
+{
+ // We're in the process of redirecting - if the HTTP status code says so and
+ // followRedirect is switched on
+ return (QHttpNetworkReply::isHttpRedirect(statusCode)
+ && request.isFollowRedirects());
+}
+
bool QHttpNetworkReplyPrivate::shouldEmitSignals()
{
// for 401 & 407 don't emit the data signals. Content along with these
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
index 0fe298da27..6e81663500 100644
--- a/src/network/access/qhttpnetworkreply_p.h
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -127,8 +127,15 @@ public:
bool isSpdyUsed() const;
void setSpdyWasUsed(bool spdy);
+ bool isRedirecting() const;
+
QHttpNetworkConnection* connection();
+ QUrl redirectUrl() const;
+ void setRedirectUrl(const QUrl &url);
+
+ static bool isHttpRedirect(int statusCode);
+
#ifndef QT_NO_SSL
QSslConfiguration sslConfiguration() const;
void setSslConfiguration(const QSslConfiguration &config);
@@ -153,6 +160,7 @@ Q_SIGNALS:
void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
#endif
void authenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
+ void redirected(const QUrl &url, int httpStatus, int maxRedirectsRemaining);
private:
Q_DECLARE_PRIVATE(QHttpNetworkReply)
friend class QHttpSocketEngine;
@@ -185,6 +193,7 @@ public:
qint64 readReplyBodyChunked(QAbstractSocket *in, QByteDataBuffer *out);
qint64 getChunkSize(QAbstractSocket *in, qint64 *chunkSize);
+ bool isRedirecting() const;
bool shouldEmitSignals();
bool expectContent();
void eraseData();
@@ -245,6 +254,7 @@ public:
bool downstreamLimited;
char* userProvidedDownloadBuffer;
+ QUrl redirectUrl;
#ifndef QT_NO_COMPRESS
z_stream_s *inflateStrm;
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index ea1cd3d591..64172fc4fd 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -42,7 +42,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Oper
QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
: QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false),
- withCredentials(true), preConnect(false)
+ withCredentials(true), preConnect(false), followRedirect(false), redirectCount(0)
{
}
@@ -59,6 +59,8 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
withCredentials = other.withCredentials;
ssl = other.ssl;
preConnect = other.preConnect;
+ followRedirect = other.followRedirect;
+ redirectCount = other.redirectCount;
}
QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
@@ -217,6 +219,26 @@ void QHttpNetworkRequest::setPreConnect(bool preConnect)
d->preConnect = preConnect;
}
+bool QHttpNetworkRequest::isFollowRedirects() const
+{
+ return d->followRedirect;
+}
+
+void QHttpNetworkRequest::setFollowRedirects(bool followRedirect)
+{
+ d->followRedirect = followRedirect;
+}
+
+int QHttpNetworkRequest::redirectCount() const
+{
+ return d->redirectCount;
+}
+
+void QHttpNetworkRequest::setRedirectCount(int count)
+{
+ d->redirectCount = count;
+}
+
qint64 QHttpNetworkRequest::contentLength() const
{
return d->contentLength();
diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h
index d136f22b7d..1add3c6150 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -118,6 +118,12 @@ public:
bool isPreConnect() const;
void setPreConnect(bool preConnect);
+ bool isFollowRedirects() const;
+ void setFollowRedirects(bool followRedirect);
+
+ int redirectCount() const;
+ void setRedirectCount(int count);
+
void setUploadByteDevice(QNonContiguousByteDevice *bd);
QNonContiguousByteDevice* uploadByteDevice() const;
@@ -154,6 +160,8 @@ public:
bool withCredentials;
bool ssl;
bool preConnect;
+ bool followRedirect;
+ int redirectCount;
};
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index be6fa01098..3fc4fa9dee 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -496,6 +496,9 @@ void QHttpThreadDelegate::finishedSlot()
emit error(statusCodeFromHttp(httpReply->statusCode(), httpRequest.url()), msg);
}
+ if (httpRequest.isFollowRedirects() && httpReply->isRedirecting())
+ emit redirected(httpReply->redirectUrl(), httpReply->statusCode(), httpReply->request().redirectCount() - 1);
+
emit downloadFinished();
QMetaObject::invokeMethod(httpReply, "deleteLater", Qt::QueuedConnection);
diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
index ff919b11c5..4889bcd1f1 100644
--- a/src/network/access/qhttpthreaddelegate_p.h
+++ b/src/network/access/qhttpthreaddelegate_p.h
@@ -139,6 +139,8 @@ signals:
void downloadData(QByteArray);
void error(QNetworkReply::NetworkError, const QString);
void downloadFinished();
+ void redirected(const QUrl &url, int httpStatus, int maxRedirectsRemainig);
+
public slots:
// This are called via QueuedConnection from user thread
void startRequest();
diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp
index 3e77e1b874..26f7034c09 100644
--- a/src/network/access/qnetworkreply.cpp
+++ b/src/network/access/qnetworkreply.cpp
@@ -132,6 +132,14 @@ QNetworkReplyPrivate::QNetworkReplyPrivate()
\value BackgroundRequestNotAllowedError the background request
is not currently allowed due to platform policy.
+ \value TooManyRedirectsError while following redirects, the maximum
+ limit was reached. The limit is by default set to 50 or as set by
+ QNetworkRequest::setMaxRedirectsAllowed().
+
+ \value InsecureRedirectError while following redirects, the network
+ access API detected a redirect from a encrypted protocol (https) to an
+ unencrypted one (http).
+
\value ProxyConnectionRefusedError the connection to the proxy
server was refused (the proxy server is not accepting requests)
@@ -276,6 +284,19 @@ QNetworkReplyPrivate::QNetworkReplyPrivate()
*/
/*!
+ \fn void QNetworkReply::redirected(const QUrl &url)
+ \since 5.6
+
+ This signal is emitted if the QNetworkRequest::FollowRedirectsAttribute was
+ set in the request and the server responded with a 3xx status (specifically
+ 301, 302, 303, 305 or 307 status code) with a valid url in the location
+ header, indicating a HTTP redirect. The \a url parameter contains the new
+ redirect url as returned by the server in the location header.
+
+ \sa QNetworkRequest::FollowRedirectsAttribute
+*/
+
+/*!
\fn void QNetworkReply::metaDataChanged()
\omit FIXME: Update name? \endomit
@@ -498,7 +519,7 @@ QNetworkAccessManager *QNetworkReply::manager() const
*/
QNetworkRequest QNetworkReply::request() const
{
- return d_func()->request;
+ return d_func()->originalRequest;
}
/*!
@@ -549,9 +570,12 @@ bool QNetworkReply::isRunning() const
/*!
Returns the URL of the content downloaded or uploaded. Note that
- the URL may be different from that of the original request.
+ the URL may be different from that of the original request. If the
+ QNetworkRequest::FollowRedirectsAttribute was set in the request, then this
+ function returns the current url that the network API is accessing, i.e the
+ url emitted in the QNetworkReply::redirected signal.
- \sa request(), setUrl(), QNetworkRequest::url()
+ \sa request(), setUrl(), QNetworkRequest::url(), redirected()
*/
QUrl QNetworkReply::url() const
{
@@ -794,7 +818,7 @@ void QNetworkReply::setOperation(QNetworkAccessManager::Operation operation)
void QNetworkReply::setRequest(const QNetworkRequest &request)
{
Q_D(QNetworkReply);
- d->request = request;
+ d->originalRequest = request;
}
/*!
diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h
index 91c9e77d8d..672a69e12b 100644
--- a/src/network/access/qnetworkreply.h
+++ b/src/network/access/qnetworkreply.h
@@ -69,6 +69,8 @@ public:
TemporaryNetworkFailureError,
NetworkSessionFailedError,
BackgroundRequestNotAllowedError,
+ TooManyRedirectsError,
+ InsecureRedirectError,
UnknownNetworkError = 99,
// proxy errors (101-199):
@@ -153,6 +155,7 @@ Q_SIGNALS:
void sslErrors(const QList<QSslError> &errors);
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
#endif
+ void redirected(const QUrl &url);
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
diff --git a/src/network/access/qnetworkreply_p.h b/src/network/access/qnetworkreply_p.h
index f5fe7bd844..2a3a2e4297 100644
--- a/src/network/access/qnetworkreply_p.h
+++ b/src/network/access/qnetworkreply_p.h
@@ -69,6 +69,7 @@ public:
QNetworkReplyPrivate();
QNetworkRequest request;
+ QNetworkRequest originalRequest;
QUrl url;
QPointer<QNetworkAccessManager> manager;
qint64 readBufferMaxSize;
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index c92c360420..47621f41d8 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -166,6 +166,7 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage
d->manager = manager;
d->managerPrivate = manager->d_func();
d->request = request;
+ d->originalRequest = request;
d->operation = operation;
d->outgoingData = outgoingData;
d->url = request.url();
@@ -482,7 +483,7 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
if (!nc)
return false; // no local cache
- QNetworkCacheMetaData metaData = nc->metaData(request.url());
+ QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
if (!metaData.isValid())
return false; // not in cache
@@ -596,7 +597,7 @@ QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(const QNetwo
}
}
-void QNetworkReplyHttpImplPrivate::postRequest()
+void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpRequest)
{
Q_Q(QNetworkReplyHttpImpl);
@@ -620,8 +621,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
thread = managerPrivate->httpThread;
}
- QUrl url = request.url();
+ QUrl url = newHttpRequest.url();
httpRequest.setUrl(url);
+ httpRequest.setRedirectCount(newHttpRequest.maximumRedirectsAllowed());
QString scheme = url.scheme().toLower();
bool ssl = (scheme == QLatin1String("https")
@@ -637,7 +639,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
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, managerPrivate->queryProxy(QNetworkProxyQuery(newHttpRequest.url()))) {
// use the first proxy that works
// for non-encrypted connections, any transparent or HTTP proxy
// for encrypted, only transparent proxies
@@ -668,9 +670,11 @@ void QNetworkReplyHttpImplPrivate::postRequest()
}
#endif
+ if (newHttpRequest.attribute(QNetworkRequest::FollowRedirectsAttribute).toBool())
+ httpRequest.setFollowRedirects(true);
bool loadedFromCache = false;
- httpRequest.setPriority(convert(request.priority()));
+ httpRequest.setPriority(convert(newHttpRequest.priority()));
switch (operation) {
case QNetworkAccessManager::GetOperation:
@@ -704,7 +708,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
invalidateCache(); // for safety reasons, we don't know what the operation does
httpRequest.setOperation(QHttpNetworkRequest::Custom);
createUploadByteDevice();
- httpRequest.setCustomVerb(request.attribute(
+ httpRequest.setCustomVerb(newHttpRequest.attribute(
QNetworkRequest::CustomVerbAttribute).toByteArray());
break;
@@ -716,7 +720,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
return; // no need to send the request! :)
}
- QList<QByteArray> headers = request.rawHeaderList();
+ QList<QByteArray> headers = newHttpRequest.rawHeaderList();
if (resumeOffset != 0) {
if (headers.contains("Range")) {
// Need to adjust resume offset for user specified range
@@ -724,7 +728,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
headers.removeOne("Range");
// We've already verified that requestRange starts with "bytes=", see canResume.
- QByteArray requestRange = request.rawHeader("Range").mid(6);
+ QByteArray requestRange = newHttpRequest.rawHeader("Range").mid(6);
int index = requestRange.indexOf('-');
@@ -741,16 +745,16 @@ void QNetworkReplyHttpImplPrivate::postRequest()
}
foreach (const QByteArray &header, headers)
- httpRequest.setHeaderField(header, request.rawHeader(header));
+ httpRequest.setHeaderField(header, newHttpRequest.rawHeader(header));
- if (request.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
+ if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
httpRequest.setPipeliningAllowed(true);
if (request.attribute(QNetworkRequest::SpdyAllowedAttribute).toBool() == true)
httpRequest.setSPDYAllowed(true);
if (static_cast<QNetworkRequest::LoadControl>
- (request.attribute(QNetworkRequest::AuthenticationReuseAttribute,
+ (newHttpRequest.attribute(QNetworkRequest::AuthenticationReuseAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
httpRequest.setWithCredentials(false);
@@ -777,7 +781,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
delegate->ssl = ssl;
#ifndef QT_NO_SSL
if (ssl)
- delegate->incomingSslConfiguration = request.sslConfiguration();
+ delegate->incomingSslConfiguration = newHttpRequest.sslConfiguration();
#endif
// Do we use synchronous HTTP?
@@ -789,7 +793,7 @@ void QNetworkReplyHttpImplPrivate::postRequest()
if (!synchronous) {
// Tell our zerocopy policy to the delegate
- QVariant downloadBufferMaximumSizeAttribute = request.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
+ QVariant downloadBufferMaximumSizeAttribute = newHttpRequest.attribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute);
if (downloadBufferMaximumSizeAttribute.isValid()) {
delegate->downloadBufferMaximumSize = downloadBufferMaximumSizeAttribute.toLongLong();
} else {
@@ -824,6 +828,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
QObject::connect(delegate, SIGNAL(error(QNetworkReply::NetworkError,QString)),
q, SLOT(httpError(QNetworkReply::NetworkError,QString)),
Qt::QueuedConnection);
+ QObject::connect(delegate, SIGNAL(redirected(QUrl,int,int)),
+ q, SLOT(onRedirected(QUrl,int,int)),
+ Qt::QueuedConnection);
#ifndef QT_NO_SSL
QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
@@ -950,7 +957,7 @@ void QNetworkReplyHttpImplPrivate::invalidateCache()
{
QAbstractNetworkCache *nc = managerPrivate->networkCache;
if (nc)
- nc->remove(request.url());
+ nc->remove(httpRequest.url());
}
void QNetworkReplyHttpImplPrivate::initCacheSaveDevice()
@@ -1030,21 +1037,30 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
for (int i = 0; i < pendingDownloadDataCopy.bufferCount(); i++) {
QByteArray const &item = pendingDownloadDataCopy[i];
+ // This is going to look a little strange. When downloading data while a
+ // HTTP redirect is happening (and enabled), we write the redirect
+ // response to the cache. However, we do not append it to our internal
+ // buffer as that will contain the response data only for the final
+ // response
if (cacheSaveDevice)
cacheSaveDevice->write(item.constData(), item.size());
- downloadMultiBuffer.append(item);
+
+ if (!isHttpRedirectResponse())
+ downloadMultiBuffer.append(item);
bytesWritten += item.size();
}
pendingDownloadDataCopy.clear();
- bytesDownloaded += bytesWritten;
-
-
QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
if (preMigrationDownloaded != Q_INT64_C(-1))
totalSize = totalSize.toLongLong() + preMigrationDownloaded;
+ if (isHttpRedirectResponse())
+ return;
+
+ bytesDownloaded += bytesWritten;
+
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).
@@ -1066,6 +1082,64 @@ void QNetworkReplyHttpImplPrivate::replyFinished()
finished();
}
+QNetworkAccessManager::Operation QNetworkReplyHttpImplPrivate::getRedirectOperation(QNetworkAccessManager::Operation currentOp, int httpStatus)
+{
+ // HTTP status code can be used to decide if we can redirect with a GET
+ // operation or not. See http://www.ietf.org/rfc/rfc2616.txt [Sec 10.3] for
+ // more details
+ Q_UNUSED(httpStatus);
+
+ switch (currentOp) {
+ case QNetworkAccessManager::HeadOperation:
+ return QNetworkAccessManager::HeadOperation;
+ default:
+ break;
+ }
+ // For now, we're always returning GET for anything other than HEAD
+ return QNetworkAccessManager::GetOperation;
+}
+
+bool QNetworkReplyHttpImplPrivate::isHttpRedirectResponse() const
+{
+ return httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(statusCode);
+}
+
+QNetworkRequest QNetworkReplyHttpImplPrivate::createRedirectRequest(const QNetworkRequest &originalRequest,
+ const QUrl &url,
+ int maxRedirectsRemaining)
+{
+ QNetworkRequest newRequest(originalRequest);
+ newRequest.setUrl(url);
+ newRequest.setMaximumRedirectsAllowed(maxRedirectsRemaining);
+
+ return newRequest;
+}
+
+void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemaining)
+{
+ Q_Q(QNetworkReplyHttpImpl);
+
+ if (isFinished)
+ return;
+
+ if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
+ url = redirectUrl;
+
+ QNetworkRequest redirectRequest = createRedirectRequest(originalRequest, redirectUrl, maxRedirectsRemaining);
+ operation = getRedirectOperation(operation, httpStatus);
+
+ cookedHeaders.clear();
+
+ if (managerPrivate->httpThread)
+ managerPrivate->httpThread->disconnect();
+
+ // Recurse
+ QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection,
+ Q_ARG(QNetworkRequest, redirectRequest));
+
+ emit q->redirected(redirectUrl);
+}
+
void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
{
Q_Q(QNetworkReplyHttpImpl);
@@ -1114,7 +1188,16 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
end = headerMap.constEnd();
for (; it != end; ++it) {
QByteArray value = q->rawHeader(it->first);
+
+ // Reset any previous "location" header set in the reply. In case of
+ // redirects, we don't want to 'append' multiple location header values,
+ // rather we keep only the latest one
+ if (it->first.toLower() == "location")
+ value.clear();
+
if (!value.isEmpty()) {
+ // Why are we appending values for headers which are already
+ // present?
if (qstricmp(it->first.constData(), "set-cookie") == 0)
value += '\n';
else
@@ -1128,12 +1211,13 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
// is it a redirection?
- checkForRedirect(statusCode);
+ if (!isHttpRedirectResponse())
+ checkForRedirect(statusCode);
if (statusCode >= 500 && statusCode < 600) {
QAbstractNetworkCache *nc = managerPrivate->networkCache;
if (nc) {
- QNetworkCacheMetaData metaData = nc->metaData(request.url());
+ QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
QNetworkHeadersPrivate cacheHeaders;
cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
@@ -1155,7 +1239,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
#endif
QAbstractNetworkCache *nc = managerPrivate->networkCache;
if (nc) {
- QNetworkCacheMetaData oldMetaData = nc->metaData(request.url());
+ QNetworkCacheMetaData oldMetaData = nc->metaData(httpRequest.url());
QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
if (oldMetaData != metaData)
nc->updateMetaData(metaData);
@@ -1202,6 +1286,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceive
// FIXME where is it closed?
}
+ if (isHttpRedirectResponse())
+ return;
+
bytesDownloaded = bytesReceived;
downloadBufferCurrentSize = bytesReceived;
@@ -1350,6 +1437,8 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData
if (status < 100)
status = 200; // fake it
+ statusCode = status;
+
q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
@@ -1357,10 +1446,16 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData
QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
end = rawHeaders.constEnd();
- for ( ; it != end; ++it)
+ QUrl redirectUrl;
+ for ( ; it != end; ++it) {
+ if (httpRequest.isFollowRedirects() &&
+ !qstricmp(it->first.toLower().constData(), "location"))
+ redirectUrl = QUrl::fromEncoded(it->second);
setRawHeader(it->first, it->second);
+ }
- checkForRedirect(status);
+ if (!isHttpRedirectResponse())
+ checkForRedirect(status);
cacheLoadDevice = contents;
q->connect(cacheLoadDevice, SIGNAL(readyRead()), SLOT(_q_cacheLoadReadyRead()));
@@ -1377,6 +1472,14 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData
qDebug() << "Successfully sent cache:" << url << contents->size() << "bytes";
#endif
+ // Do redirect processing
+ if (httpRequest.isFollowRedirects() && QHttpNetworkReply::isHttpRedirect(status)) {
+ QMetaObject::invokeMethod(q, "onRedirected", Qt::QueuedConnection,
+ Q_ARG(QUrl, redirectUrl),
+ Q_ARG(int, status),
+ Q_ARG(int, httpRequest.redirectCount() - 1));
+ }
+
// Set the following flag so we can ignore some signals from HTTP thread
// that would still come
loadingFromCache = true;
@@ -1572,13 +1675,13 @@ void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset)
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()
+bool QNetworkReplyHttpImplPrivate::start(const QNetworkRequest &newHttpRequest)
{
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession(managerPrivate->getNetworkSession());
if (!networkSession) {
#endif
- postRequest();
+ postRequest(newHttpRequest);
return true;
#ifndef QT_NO_BEARERMANAGEMENT
}
@@ -1588,7 +1691,7 @@ bool QNetworkReplyHttpImplPrivate::start()
if (host == QLatin1String("localhost") ||
QHostAddress(host).isLoopback()) {
// Don't need an open session for localhost access.
- postRequest();
+ postRequest(newHttpRequest);
return true;
}
@@ -1597,13 +1700,13 @@ bool QNetworkReplyHttpImplPrivate::start()
Q_Q(QNetworkReplyHttpImpl);
QObject::connect(networkSession.data(), SIGNAL(usagePoliciesChanged(QNetworkSession::UsagePolicies)),
q, SLOT(_q_networkSessionUsagePoliciesChanged(QNetworkSession::UsagePolicies)));
- postRequest();
+ postRequest(newHttpRequest);
return true;
} else if (synchronous) {
// Command line applications using the synchronous path such as xmlpatterns may need an extra push.
networkSession->open();
if (networkSession->waitForOpened()) {
- postRequest();
+ postRequest(newHttpRequest);
return true;
}
}
@@ -1635,7 +1738,7 @@ void QNetworkReplyHttpImplPrivate::_q_startOperation()
}
#endif
- if (!start()) {
+ if (!start(request)) {
#ifndef QT_NO_BEARERMANAGEMENT
// backend failed to start because the session state is not Connected.
// QNetworkAccessManager will call reply->backend->start() again for us when the session
@@ -1697,18 +1800,20 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
// 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();
- if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
- downloadProgressSignalChoke.restart();
- emit q->downloadProgress(bytesDownloaded,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
- }
+ if (!(isHttpRedirectResponse())) {
+ // This readyRead() goes to the user. The user then may or may not read() anything.
+ emit q->readyRead();
+ if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
+ downloadProgressSignalChoke.restart();
+ 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()) {
+ while (cacheLoadDevice->bytesAvailable() && !isHttpRedirectResponse()) {
downloadMultiBuffer.append(cacheLoadDevice->readAll());
}
@@ -1731,7 +1836,6 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
cacheLoadDevice = 0;
QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
}
-
}
@@ -1951,6 +2055,15 @@ void QNetworkReplyHttpImplPrivate::finished()
#endif
}
+ // if we don't know the total size of or we received everything save the cache
+ if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
+ completeCacheSave();
+
+ // We check for errorCode too as in case of SSL handshake failure, we still
+ // get the HTTP redirect status code (301, 303 etc)
+ if (isHttpRedirectResponse() && errorCode == QNetworkReply::NoError)
+ return;
+
state = Finished;
q->setFinished(true);
@@ -1963,10 +2076,6 @@ void QNetworkReplyHttpImplPrivate::finished()
if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
emit q->uploadProgress(0, 0);
- // if we don't know the total size of or we received everything save the cache
- if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
- completeCacheSave();
-
emit q->readChannelFinished();
emit q->finished();
}
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
index afe807ea59..08f3fcf342 100644
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -89,6 +89,7 @@ public:
Q_DECLARE_PRIVATE(QNetworkReplyHttpImpl)
Q_PRIVATE_SLOT(d_func(), void _q_startOperation())
+ Q_PRIVATE_SLOT(d_func(), bool start(const QNetworkRequest &))
Q_PRIVATE_SLOT(d_func(), void _q_cacheLoadReadyRead())
Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingData())
Q_PRIVATE_SLOT(d_func(), void _q_bufferOutgoingDataFinished())
@@ -126,7 +127,7 @@ public:
Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64))
Q_PRIVATE_SLOT(d_func(), void _q_cacheSaveDeviceAboutToClose())
Q_PRIVATE_SLOT(d_func(), void _q_metaDataChanged())
-
+ Q_PRIVATE_SLOT(d_func(), void onRedirected(const QUrl &, int, int))
#ifndef QT_NO_SSL
protected:
@@ -157,7 +158,7 @@ public:
QNetworkReplyHttpImplPrivate();
~QNetworkReplyHttpImplPrivate();
- bool start();
+ bool start(const QNetworkRequest &newHttpRequest);
void _q_startOperation();
void _q_cacheLoadReadyRead();
@@ -200,6 +201,7 @@ public:
QIODevice *outgoingData;
QSharedPointer<QRingBuffer> outgoingDataBuffer;
void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); // dup?
+ void onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemainig);
qint64 bytesUploaded;
@@ -259,9 +261,10 @@ public:
QNetworkCacheMetaData fetchCacheMetaData(const QNetworkCacheMetaData &metaData) const;
- void postRequest();
-
-
+ void postRequest(const QNetworkRequest& newHttpRequest);
+ QNetworkAccessManager::Operation getRedirectOperation(QNetworkAccessManager::Operation currentOp, int httpStatus);
+ QNetworkRequest createRedirectRequest(const QNetworkRequest &originalRequests, const QUrl &url, int maxRedirectsRemainig);
+ bool isHttpRedirectResponse() const;
public:
// From HTTP thread:
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index c7cb16c71c..94e2b437b3 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -140,7 +140,9 @@ QT_BEGIN_NAMESPACE
request to a different URL. The Network Access API does not by
default follow redirections: it's up to the application to
determine if the requested redirection should be allowed,
- according to its security policies.
+ according to its security policies. However, if
+ QNetworkRequest::FollowRedirectsAttribute is set, then this attribute
+ will not be present in the reply.
The returned URL might be relative. Use QUrl::resolved()
to create an absolute URL out of it.
@@ -256,6 +258,12 @@ QT_BEGIN_NAMESPACE
in 100 millisecond intervals.
(This value was introduced in 5.5.)
+ \value FollowRedirectsAttribute
+ Requests only, type: QMetaType::Bool (default: false)
+ Indicates whether the Network Access API should automatically follow a
+ HTTP redirect response or not. Currently redirects that are insecure,
+ that is redirecting from "https" to "http" protocol, are not allowed.
+
\value User
Special type. Additional information can be passed in
QVariants with types ranging from User to UserMax. The default
@@ -306,11 +314,13 @@ QT_BEGIN_NAMESPACE
class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
{
public:
+ static const int maxRedirectCount = 50;
inline QNetworkRequestPrivate()
: priority(QNetworkRequest::NormalPriority)
#ifndef QT_NO_SSL
, sslConfiguration(0)
#endif
+ , maxRedirectsAllowed(maxRedirectCount)
{ qRegisterMetaType<QNetworkRequest>(); }
~QNetworkRequestPrivate()
{
@@ -325,7 +335,7 @@ public:
{
url = other.url;
priority = other.priority;
-
+ maxRedirectsAllowed = other.maxRedirectsAllowed;
#ifndef QT_NO_SSL
sslConfiguration = 0;
if (other.sslConfiguration)
@@ -338,7 +348,8 @@ public:
return url == other.url &&
priority == other.priority &&
rawHeaders == other.rawHeaders &&
- attributes == other.attributes;
+ attributes == other.attributes &&
+ maxRedirectsAllowed == other.maxRedirectsAllowed;
// don't compare cookedHeaders
}
@@ -347,6 +358,7 @@ public:
#ifndef QT_NO_SSL
mutable QSslConfiguration *sslConfiguration;
#endif
+ int maxRedirectsAllowed;
};
/*!
@@ -657,6 +669,32 @@ void QNetworkRequest::setPriority(Priority priority)
d->priority = priority;
}
+/*!
+ \since 5.6
+
+ Returns the maximum number of redirects allowed to be followed for this
+ request.
+
+ \sa setMaximumRedirectsAllowed()
+*/
+int QNetworkRequest::maximumRedirectsAllowed() const
+{
+ return d->maxRedirectsAllowed;
+}
+
+/*!
+ \since 5.6
+
+ Sets the maximum number of redirects allowed to be followed for this
+ request to \a maxRedirectsAllowed.
+
+ \sa maximumRedirectsAllowed()
+*/
+void QNetworkRequest::setMaximumRedirectsAllowed(int maxRedirectsAllowed)
+{
+ d->maxRedirectsAllowed = maxRedirectsAllowed;
+}
+
static QByteArray headerName(QNetworkRequest::KnownHeaders header)
{
switch (header) {
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index d6d907bdc4..c726db78cb 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -81,6 +81,7 @@ public:
SpdyAllowedAttribute,
SpdyWasUsedAttribute,
EmitAllUploadProgressSignalsAttribute,
+ FollowRedirectsAttribute,
User = 1000,
UserMax = 32767
@@ -141,6 +142,10 @@ public:
Priority priority() const;
void setPriority(Priority priority);
+ // HTTP redirect related
+ int maximumRedirectsAllowed() const;
+ void setMaximumRedirectsAllowed(int maximumRedirectsAllowed);
+
private:
QSharedDataPointer<QNetworkRequestPrivate> d;
friend class QNetworkRequestPrivate;
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
index 3c3c5755eb..cd14e42f8f 100644
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -121,6 +121,14 @@ class tst_QNetworkReply: public QObject
return s;
};
+ static QString tempRedirectReplyStr() {
+ QString s = "HTTP/1.1 307 Temporary Redirect\r\n"
+ "Content-Type: text/plain\r\n"
+ "location: %1\r\n"
+ "\r\n";
+ return s;
+ };
+
QEventLoop *loop;
enum RunSimpleRequestReturn { Timeout = 0, Success, Failure };
int returnCode;
@@ -458,6 +466,11 @@ private Q_SLOTS:
void putWithRateLimiting();
+ void ioHttpSingleRedirect();
+ void ioHttpChangeMaxRedirects();
+ void ioHttpRedirectErrors_data();
+ void ioHttpRedirectErrors();
+
// NOTE: This test must be last!
void parentingRepliesToTheApp();
private:
@@ -595,10 +608,16 @@ protected:
virtual void reply() {
Q_ASSERT(!client.isNull());
// we need to emulate the bytesWrittenSlot call if the data is empty.
- if (dataToTransmit.size() == 0)
+ if (dataToTransmit.size() == 0) {
QMetaObject::invokeMethod(this, "bytesWrittenSlot", Qt::QueuedConnection);
- else
+ } else {
client->write(dataToTransmit);
+ // FIXME: For SSL connections, if we don't flush the socket, the
+ // client never receives the data and since we're doing a disconnect
+ // immediately afterwards, it causes a RemoteHostClosedError for the
+ // client
+ client->flush();
+ }
}
private:
void connectSocketSignals()
@@ -7958,7 +7977,142 @@ void tst_QNetworkReply::putWithRateLimiting()
QCOMPARE(uploadedData, data);
}
+void tst_QNetworkReply::ioHttpSingleRedirect()
+{
+ QUrl localhost = QUrl("http://localhost");
+ QByteArray http200Reply = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
+
+ // Setup server to which the second server will redirect to
+ MiniHttpServer server2(http200Reply);
+
+ QUrl redirectUrl = QUrl(localhost);
+ redirectUrl.setPort(server2.serverPort());
+
+ QByteArray tempRedirectReply =
+ tempRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1();
+
+
+ // Setup redirect server
+ MiniHttpServer server(tempRedirectReply);
+
+ localhost.setPort(server.serverPort());
+ QNetworkRequest request(localhost);
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+
+ QNetworkReplyPtr reply(manager.get(request));
+ QSignalSpy redSpy(reply.data(), SIGNAL(redirected(QUrl)));
+ QSignalSpy finSpy(reply.data(), SIGNAL(finished()));
+
+ QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply));
+
+ // Redirected and finished should be emitted exactly once
+ QCOMPARE(redSpy.count(), 1);
+ QCOMPARE(finSpy.count(), 1);
+
+ // Original URL should not be changed after redirect
+ QCOMPARE(request.url(), localhost);
+
+ // Verify Redirect url
+ QList<QVariant> args = redSpy.takeFirst();
+ QCOMPARE(args.at(0).toUrl(), redirectUrl);
+
+ // Reply url is set to the redirect url
+ QCOMPARE(reply->url(), redirectUrl);
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+}
+
+void tst_QNetworkReply::ioHttpChangeMaxRedirects()
+{
+ QUrl localhost = QUrl("http://localhost");
+ QByteArray http200Reply = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
+
+ MiniHttpServer server1("");
+ MiniHttpServer server2("");
+ MiniHttpServer server3(http200Reply);
+
+ QUrl server2Url(localhost);
+ server2Url.setPort(server2.serverPort());
+ server1.setDataToTransmit(tempRedirectReplyStr().arg(
+ QString(server2Url.toEncoded())).toLatin1());
+
+ QUrl server3Url(localhost);
+ server3Url.setPort(server3.serverPort());
+ server2.setDataToTransmit(tempRedirectReplyStr().arg(
+ QString(server3Url.toEncoded())).toLatin1());
+
+ localhost.setPort(server1.serverPort());
+ QNetworkRequest request(localhost);
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+
+ // Set Max redirects to 1. This will cause TooManyRedirectsError
+ request.setMaximumRedirectsAllowed(1);
+
+ QNetworkReplyPtr reply(manager.get(request));
+ QSignalSpy redSpy(reply.data(), SIGNAL(redirected(QUrl)));
+ QSignalSpy spy(reply.data(), SIGNAL(error(QNetworkReply::NetworkError)));
+
+ QVERIFY(waitForFinish(reply) == Failure);
+
+ QCOMPARE(redSpy.count(), request.maximumRedirectsAllowed());
+ QCOMPARE(spy.count(), 1);
+ QVERIFY(reply->error() == QNetworkReply::TooManyRedirectsError);
+
+ // Increase max redirects to allow successful completion
+ request.setMaximumRedirectsAllowed(3);
+
+ QNetworkReplyPtr reply2(manager.get(request));
+ QSignalSpy redSpy2(reply2.data(), SIGNAL(redirected(QUrl)));
+
+ QVERIFY2(waitForFinish(reply2) == Success, msgWaitForFinished(reply2));
+
+ QCOMPARE(redSpy2.count(), 2);
+ QCOMPARE(reply2->url(), server3Url);
+ QVERIFY(reply2->error() == QNetworkReply::NoError);
+}
+
+void tst_QNetworkReply::ioHttpRedirectErrors_data()
+{
+ QTest::addColumn<QString>("url");
+ QTest::addColumn<QString>("dataToSend");
+ QTest::addColumn<QNetworkReply::NetworkError>("error");
+
+ QString tempRedirectReply = QString("HTTP/1.1 307 Temporary Redirect\r\n"
+ "Content-Type: text/plain\r\n"
+ "location: http://localhost:%1\r\n\r\n");
+
+ QTest::newRow("too-many-redirects") << "http://localhost" << tempRedirectReply << QNetworkReply::TooManyRedirectsError;
+ QTest::newRow("insecure-redirect") << "https://localhost" << tempRedirectReply << QNetworkReply::InsecureRedirectError;
+ QTest::newRow("unknown-redirect") << "http://localhost"<< tempRedirectReply.replace("http", "bad_protocol") << QNetworkReply::ProtocolUnknownError;
+}
+
+void tst_QNetworkReply::ioHttpRedirectErrors()
+{
+ QFETCH(QString, url);
+ QFETCH(QString, dataToSend);
+ QFETCH(QNetworkReply::NetworkError, error);
+
+ QUrl localhost(url);
+ MiniHttpServer server("", localhost.scheme() == "https");
+
+ localhost.setPort(server.serverPort());
+
+ QByteArray d2s = dataToSend.arg(
+ QString::number(server.serverPort())).toLatin1();
+ server.setDataToTransmit(d2s);
+
+ QNetworkRequest request(localhost);
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+ QNetworkReplyPtr reply(manager.get(request));
+ if (localhost.scheme() == "https")
+ reply.data()->ignoreSslErrors();
+ QSignalSpy spy(reply.data(), SIGNAL(error(QNetworkReply::NetworkError)));
+
+ QVERIFY(waitForFinish(reply) == Failure);
+
+ QCOMPARE(spy.count(), 1);
+ QVERIFY(reply->error() == error);
+}
// NOTE: This test must be last testcase in tst_qnetworkreply!
void tst_QNetworkReply::parentingRepliesToTheApp()