From cebf1fea4a6802b8999469f647f52171e87d02b6 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 28 Dec 2016 15:27:57 +0100 Subject: Add redirects policy to QNetworkAccessManager This patch makes it possible to enable/disable redirects on QNAM level (before it was per-request only). This policy would be applied to all subsequent requests* created by QNAM. The policies we support at the moment: a. Manual - that's what we always had - it's up to a user to handle redirects. b. NoLessSafeRedirectsPolicy - we allow http->http, http->https and https->https redirects, but no protocol 'downgrade' (no https->http redirects). c. SameOriginPolicy - we check that protocol/host/port are the same. Updated tst_qnetworkreply. *We previously were enabling redirect for each request, by setting FollowRedirectsAttribute on QNetworkRequest object. For backward compatibility this attribute has a higher priority (if set) than QNAM's policy (and it will work as NoLessSafeRedirectsPolicy). [ChangeLog][QtNetwork] Added redirects policy to QNAM Task-number: QTPM-239 Task-number: QTPM-237 Change-Id: I493d1728254b71b61b5504937e8e01dca5953527 Reviewed-by: Timur Pocheptsov --- src/network/access/qhttpnetworkconnection.cpp | 41 +++-- src/network/access/qhttpnetworkrequest.cpp | 21 ++- src/network/access/qhttpnetworkrequest_p.h | 6 +- src/network/access/qnetworkaccessmanager.cpp | 56 ++++++- src/network/access/qnetworkaccessmanager.h | 6 +- src/network/access/qnetworkaccessmanager_p.h | 3 + src/network/access/qnetworkreplyhttpimpl.cpp | 10 +- src/network/access/qnetworkrequest.cpp | 26 ++++ src/network/access/qnetworkrequest.h | 10 +- .../access/qnetworkreply/tst_qnetworkreply.cpp | 169 +++++++++++++++++++++ 10 files changed, 316 insertions(+), 32 deletions(-) diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 128f75f93b..e5c6c2f81c 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -522,17 +522,17 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke if (!reply->request().isFollowRedirects()) return QUrl(); - QUrl rUrl; + QUrl redirectUrl; const QList > fields = reply->header(); for (const QNetworkReply::RawHeaderPair &header : fields) { if (header.first.toLower() == "location") { - rUrl = QUrl::fromEncoded(header.second); + redirectUrl = QUrl::fromEncoded(header.second); break; } } // If the location url is invalid/empty, we emit ProtocolUnknownError - if (!rUrl.isValid()) { + if (!redirectUrl.isValid()) { emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); return QUrl(); } @@ -544,24 +544,37 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke } // Resolve the URL if it's relative - if (rUrl.isRelative()) - rUrl = reply->request().url().resolved(rUrl); + if (redirectUrl.isRelative()) + redirectUrl = reply->request().url().resolved(redirectUrl); // 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 QUrl(); + const QUrl priorUrl(reply->request().url()); + if (redirectUrl.scheme() == QLatin1String("http") || redirectUrl.scheme() == QLatin1String("https")) { + switch (reply->request().redirectsPolicy()) { + case QNetworkRequest::NoLessSafeRedirectsPolicy: + // Check if we're doing an unsecure redirect (https -> http) + if (priorUrl.scheme() == QLatin1String("https") + && redirectUrl.scheme() == QLatin1String("http")) { + emitReplyError(socket, reply, QNetworkReply::InsecureRedirectError); + return QUrl(); + } + break; + case QNetworkRequest::SameOriginRedirectsPolicy: + if (priorUrl.host() != redirectUrl.host() + || priorUrl.scheme() != redirectUrl.scheme() + || priorUrl.port() != redirectUrl.port()) { + emitReplyError(socket, reply, QNetworkReply::InsecureRedirectError); + return QUrl(); + } + break; + default: + Q_ASSERT(!"Unexpected redirect policy"); } } else { emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError); return QUrl(); } - return rUrl; + return redirectUrl; } void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request) diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 802043d847..7862d464e1 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -48,7 +48,8 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Oper QHttpNetworkRequest::Priority pri, const QUrl &newUrl) : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0), autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false), http2Allowed(false), - withCredentials(true), preConnect(false), followRedirect(false), redirectCount(0) + withCredentials(true), preConnect(false), redirectCount(0), + redirectsPolicy(QNetworkRequest::ManualRedirectsPolicy) { } @@ -65,8 +66,8 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest withCredentials(other.withCredentials), ssl(other.ssl), preConnect(other.preConnect), - followRedirect(other.followRedirect), - redirectCount(other.redirectCount) + redirectCount(other.redirectCount), + redirectsPolicy(other.redirectsPolicy) { } @@ -88,7 +89,8 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb)) && (withCredentials == other.withCredentials) && (ssl == other.ssl) - && (preConnect == other.preConnect); + && (preConnect == other.preConnect) + && (redirectsPolicy == other.redirectsPolicy); } QByteArray QHttpNetworkRequest::methodName() const @@ -229,12 +231,17 @@ void QHttpNetworkRequest::setPreConnect(bool preConnect) bool QHttpNetworkRequest::isFollowRedirects() const { - return d->followRedirect; + return d->redirectsPolicy != QNetworkRequest::ManualRedirectsPolicy; } -void QHttpNetworkRequest::setFollowRedirects(bool followRedirect) +void QHttpNetworkRequest::setRedirectsPolicy(QNetworkRequest::RedirectsPolicy policy) { - d->followRedirect = followRedirect; + d->redirectsPolicy = policy; +} + +QNetworkRequest::RedirectsPolicy QHttpNetworkRequest::redirectsPolicy() const +{ + return d->redirectsPolicy; } int QHttpNetworkRequest::redirectCount() const diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index d1abb76e28..3900e9080e 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -55,6 +55,7 @@ #ifndef QT_NO_HTTP #include +#include #include QT_BEGIN_NAMESPACE @@ -130,7 +131,8 @@ public: void setPreConnect(bool preConnect); bool isFollowRedirects() const; - void setFollowRedirects(bool followRedirect); + void setRedirectsPolicy(QNetworkRequest::RedirectsPolicy policy); + QNetworkRequest::RedirectsPolicy redirectsPolicy() const; int redirectCount() const; void setRedirectCount(int count); @@ -173,8 +175,8 @@ public: bool withCredentials; bool ssl; bool preConnect; - bool followRedirect; int redirectCount; + QNetworkRequest::RedirectsPolicy redirectsPolicy; }; diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index b763547a15..949b5563d9 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -1078,6 +1078,45 @@ void QNetworkAccessManager::connectToHost(const QString &hostName, quint16 port) get(request); } +/*! + \since 5.9 + + Sets the manager's redirects policy to be the \a policy specified. This policy + will affect all subsequent requests created by the manager. + + Use this function to enable or disable HTTP redirects on the manager's level. + + \note When creating a request QNetworkRequest::RedirectAttributePolicy has + the highest priority, next by priority is QNetworkRequest::FollowRedirectsAttribute. + Finally, the manager's policy has the lowest priority. + + For backwards compatibility the default value is QNetworkRequest::ManualRedirectsPolicy. + This may change in the future and some type of auto-redirect policy will become + the default; clients relying on manual redirect handling are encouraged to set + this policy explicitly in their code. + + \sa redirectsPolicy(), QNetworkRequest::RedirectsPolicy, + QNetworkRequest::FollowRedirectsAttribute +*/ +void QNetworkAccessManager::setRedirectsPolicy(QNetworkRequest::RedirectsPolicy policy) +{ + Q_D(QNetworkAccessManager); + d->redirectsPolicy = policy; +} + +/*! + \since 5.9 + + Returns the redirect policy that is used when creating new requests. + + \sa setRedirectsPolicy(), QNetworkRequest::RedirectsPolicy +*/ +QNetworkRequest::RedirectsPolicy QNetworkAccessManager::redirectsPolicy() const +{ + Q_D(const QNetworkAccessManager); + return d->redirectsPolicy; +} + /*! \since 4.7 @@ -1147,9 +1186,9 @@ QNetworkReply *QNetworkAccessManager::sendCustomRequest(const QNetworkRequest &r /*! Returns a new QNetworkReply object to handle the operation \a op - and request \a req. The device \a outgoingData is always 0 for Get and - Head requests, but is the value passed to post() and put() in - those operations (the QByteArray variants will pass a QBuffer + and request \a originalReq. The device \a outgoingData is always 0 + for Get and Head requests, but is the value passed to post() and + put() in those operations (the QByteArray variants will pass a QBuffer object). The default implementation calls QNetworkCookieJar::cookiesForUrl() @@ -1159,11 +1198,20 @@ QNetworkReply *QNetworkAccessManager::sendCustomRequest(const QNetworkRequest &r The returned object must be in an open state. */ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, - const QNetworkRequest &req, + const QNetworkRequest &originalReq, QIODevice *outgoingData) { Q_D(QNetworkAccessManager); + QNetworkRequest req(originalReq); + if (req.attribute(QNetworkRequest::RedirectsPolicyAttribute).isNull() + && req.attribute(QNetworkRequest::FollowRedirectsAttribute).isNull()) { + // We only apply the general manager's policy if: + // - RedirectsPolicyAttribute is not set already on request and + // - no FollowRedirectsAttribute is set. + req.setAttribute(QNetworkRequest::RedirectsPolicyAttribute, redirectsPolicy()); + } + bool isLocalFile = req.url().isLocalFile(); QString scheme = req.url().scheme(); diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index 649013cced..c0a27100e5 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -41,6 +41,7 @@ #define QNETWORKACCESSMANAGER_H #include +#include #include #ifndef QT_NO_SSL #include @@ -49,7 +50,6 @@ QT_BEGIN_NAMESPACE - class QIODevice; class QAbstractNetworkCache; class QAuthenticator; @@ -57,7 +57,6 @@ class QByteArray; template class QList; class QNetworkCookie; class QNetworkCookieJar; -class QNetworkRequest; class QNetworkReply; class QNetworkProxy; class QNetworkProxyFactory; @@ -149,6 +148,9 @@ public: #endif void connectToHost(const QString &hostName, quint16 port = 80); + void setRedirectsPolicy(QNetworkRequest::RedirectsPolicy policy); + QNetworkRequest::RedirectsPolicy redirectsPolicy() const; + Q_SIGNALS: #ifndef QT_NO_NETWORKPROXY void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h index b4b5e6a789..b996e3d58d 100644 --- a/src/network/access/qnetworkaccessmanager_p.h +++ b/src/network/access/qnetworkaccessmanager_p.h @@ -55,6 +55,7 @@ #include "qnetworkaccessmanager.h" #include "qnetworkaccesscache_p.h" #include "qnetworkaccessbackend_p.h" +#include "qnetworkrequest.h" #include "private/qobject_p.h" #include "QtNetwork/qnetworkproxy.h" #include "QtNetwork/qnetworksession.h" @@ -91,6 +92,7 @@ public: #endif cookieJarCreated(false), defaultAccessControl(true), + redirectsPolicy(QNetworkRequest::ManualRedirectsPolicy), authenticationManager(QSharedPointer::create()) { #ifndef QT_NO_BEARERMANAGEMENT @@ -193,6 +195,7 @@ public: bool cookieJarCreated; bool defaultAccessControl; + QNetworkRequest::RedirectsPolicy redirectsPolicy; // The cache with authorization data: QSharedPointer authenticationManager; diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 00f3468ebd..03ed596586 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -655,8 +655,14 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq } #endif - if (newHttpRequest.attribute(QNetworkRequest::FollowRedirectsAttribute).toBool()) - httpRequest.setFollowRedirects(true); + auto redirectsPolicy = QNetworkRequest::ManualRedirectsPolicy; + const QVariant value = newHttpRequest.attribute(QNetworkRequest::RedirectsPolicyAttribute); + if (value.isValid()) + redirectsPolicy = value.value(); + else if (newHttpRequest.attribute(QNetworkRequest::FollowRedirectsAttribute).toBool()) + redirectsPolicy = QNetworkRequest::NoLessSafeRedirectsPolicy; + + httpRequest.setRedirectsPolicy(redirectsPolicy); httpRequest.setPriority(convert(newHttpRequest.priority())); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index bc2507ca51..abc924d0e2 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -289,6 +289,12 @@ QT_BEGIN_NAMESPACE marked to be decompressed automatically. (This value was introduced in 5.9.) + \value RedirectsPolicyAttribute + Requests only, type: QMetaType::Int, should be one of the + QNetworkRequest::RedirectsPolicy values (default: ManualRedirectsPolicy). + This attribute obsoletes FollowRedirectsAttribute. + (This value was introduced in 5.9.) + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default @@ -336,6 +342,26 @@ QT_BEGIN_NAMESPACE \value Manual indicates behaviour has been manually overridden. */ +/*! + \enum QNetworkRequest::RedirectsPolicy + \since 5.9 + + Indicates whether the Network Access API should automatically follow a + HTTP redirect response or not. + + \value ManualRedirectsPolicy Default value: not following any redirects. + + \value NoLessSafeRedirectsPolicy Only "http"->"http", "http" -> "https" + or "https" -> "https" redirects are allowed. + Equivalent to setting the old FollowRedirectsAttribute + to true + + \value SameOriginRedirectsPolicy Require the same protocol, host and port. + Note, http://example.com and http://example.com:80 + will fail with this policy (implicit/explicit ports + are considered to be a mismatch). +*/ + class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate { public: diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 9a3a7f0fb5..52c398663a 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -48,7 +48,6 @@ QT_BEGIN_NAMESPACE - class QSslConfiguration; class QNetworkRequestPrivate; @@ -92,6 +91,7 @@ public: HTTP2AllowedAttribute, HTTP2WasUsedAttribute, OriginalContentLengthAttribute, + RedirectsPolicyAttribute, User = 1000, UserMax = 32767 @@ -113,6 +113,13 @@ public: LowPriority = 5 }; + enum RedirectsPolicy { + ManualRedirectsPolicy, + NoLessSafeRedirectsPolicy, + SameOriginRedirectsPolicy + }; + + explicit QNetworkRequest(const QUrl &url = QUrl()); QNetworkRequest(const QNetworkRequest &other); ~QNetworkRequest(); @@ -169,5 +176,6 @@ Q_DECLARE_SHARED(QNetworkRequest) QT_END_NAMESPACE Q_DECLARE_METATYPE(QNetworkRequest) +Q_DECLARE_METATYPE(QNetworkRequest::RedirectsPolicy) #endif diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 32446bcccd..7d07ae888c 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -476,6 +476,10 @@ private Q_SLOTS: void ioHttpChangeMaxRedirects(); void ioHttpRedirectErrors_data(); void ioHttpRedirectErrors(); + void ioHttpRedirectPolicy_data(); + void ioHttpRedirectPolicy(); + void ioHttpRedirectPolicyErrors_data(); + void ioHttpRedirectPolicyErrors(); #ifndef QT_NO_SSL void putWithServerClosingConnectionImmediately(); #endif @@ -8163,6 +8167,171 @@ void tst_QNetworkReply::ioHttpRedirectErrors() QCOMPARE(spy.count(), 1); QCOMPARE(reply->error(), error); } + +struct SameOriginRedirector : MiniHttpServer +{ + SameOriginRedirector(const QByteArray &data, bool ssl = false) + : MiniHttpServer(data, ssl) + { } + + std::vector responses; + + void reply() override + { + if (responses.empty()) { + dataToTransmit.clear(); + } else { + dataToTransmit = responses.back(); + responses.pop_back(); + } + + MiniHttpServer::reply(); + } +}; + +void tst_QNetworkReply::ioHttpRedirectPolicy_data() +{ + QTest::addColumn("policy"); + QTest::addColumn("ssl"); + QTest::addColumn("redirectCount"); + QTest::addColumn("statusCode"); + + QTest::newRow("manual-nossl") << QNetworkRequest::ManualRedirectsPolicy << false << 0 << 307; + QTest::newRow("manual-ssl") << QNetworkRequest::ManualRedirectsPolicy << true << 0 << 307; + QTest::newRow("nolesssafe-nossl") << QNetworkRequest::NoLessSafeRedirectsPolicy << false << 1 << 200; + QTest::newRow("nolesssafe-ssl") << QNetworkRequest::NoLessSafeRedirectsPolicy << true << 1 << 200; + QTest::newRow("same-origin-nossl") << QNetworkRequest::SameOriginRedirectsPolicy << false << 1 << 200; + QTest::newRow("same-origin-ssl") << QNetworkRequest::SameOriginRedirectsPolicy << true << 1 << 200; +} + +void tst_QNetworkReply::ioHttpRedirectPolicy() +{ + QFETCH(const QNetworkRequest::RedirectsPolicy, policy); + + QFETCH(const bool, ssl); +#ifdef QT_NO_SSL + if (ssl) + QSKIP("SSL is not supported"); +#endif + + QFETCH(const int, redirectCount); + QFETCH(const int, statusCode); + + // Setup HTTP server. + SameOriginRedirector redirectServer("", ssl); + + QUrl url(QLatin1String( +#ifndef QT_NO_SSL + ssl ? "https://localhost" : +#endif + "http://localhost")); + + url.setPort(redirectServer.serverPort()); + redirectServer.responses.push_back("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + redirectServer.responses.push_back(tempRedirectReplyStr().arg(QString(url.toEncoded())).toLatin1()); + + // This is the default one we preserve between tests. + QCOMPARE(manager.redirectsPolicy(), QNetworkRequest::ManualRedirectsPolicy); + + manager.setRedirectsPolicy(policy); + QCOMPARE(manager.redirectsPolicy(), policy); + QNetworkReplyPtr reply(manager.get(QNetworkRequest(url))); + if (ssl) + reply->ignoreSslErrors(); + + // Restore default: + manager.setRedirectsPolicy(QNetworkRequest::ManualRedirectsPolicy); + QSignalSpy redirectSpy(reply.data(), SIGNAL(redirected(QUrl))); + QSignalSpy finishedSpy(reply.data(), SIGNAL(finished())); + QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply)); + QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(redirectSpy.count(), redirectCount); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode); +} + +void tst_QNetworkReply::ioHttpRedirectPolicyErrors_data() +{ + QTest::addColumn("policy"); + QTest::addColumn("ssl"); + QTest::addColumn("location"); + QTest::addColumn("maxRedirects"); + QTest::addColumn("expectedError"); + + // 1. NoLessSafeRedirectsPolicy + QTest::newRow("nolesssafe-nossl-nossl-too-many") << QNetworkRequest::NoLessSafeRedirectsPolicy + << false << QString("http://localhost:%1") << 0 << QNetworkReply::TooManyRedirectsError; + QTest::newRow("nolesssafe-ssl-ssl-too-many") << QNetworkRequest::NoLessSafeRedirectsPolicy + << true << QString("https:/localhost:%1") << 0 << QNetworkReply::TooManyRedirectsError; + QTest::newRow("nolesssafe-ssl-nossl-insecure-redirect") << QNetworkRequest::NoLessSafeRedirectsPolicy + << true << QString("http://localhost:%1") << 50 << QNetworkReply::InsecureRedirectError; + // 2. SameOriginRedirectsPolicy + QTest::newRow("same-origin-nossl-nossl-too-many") << QNetworkRequest::SameOriginRedirectsPolicy + << false << QString("http://localhost:%1") << 0 << QNetworkReply::TooManyRedirectsError; + QTest::newRow("same-origin-ssl-ssl-too-many") << QNetworkRequest::SameOriginRedirectsPolicy + << true << QString("https://localhost:%1") << 0 << QNetworkReply::TooManyRedirectsError; + QTest::newRow("same-origin-https-http-wrong-protocol") << QNetworkRequest::SameOriginRedirectsPolicy + << true << QString("http://localhost:%1") << 50 << QNetworkReply::InsecureRedirectError; + QTest::newRow("same-origin-http-https-wrong-protocol") << QNetworkRequest::SameOriginRedirectsPolicy + << false << QString("https://localhost:%1") << 50 << QNetworkReply::InsecureRedirectError; + QTest::newRow("same-origin-http-http-wrong-host") << QNetworkRequest::SameOriginRedirectsPolicy + << false << QString("http://not-so-localhost:%1") << 50 << QNetworkReply::InsecureRedirectError; + QTest::newRow("same-origin-https-https-wrong-host") << QNetworkRequest::SameOriginRedirectsPolicy + << true << QString("https://not-so-localhost:%1") << 50 << QNetworkReply::InsecureRedirectError; + QTest::newRow("same-origin-http-http-wrong-port") << QNetworkRequest::SameOriginRedirectsPolicy + << false << QString("http://localhost/%1") << 50 << QNetworkReply::InsecureRedirectError; + QTest::newRow("same-origin-https-https-wrong-port") << QNetworkRequest::SameOriginRedirectsPolicy + << true << QString("https://localhost/%1") << 50 << QNetworkReply::InsecureRedirectError; +} + +void tst_QNetworkReply::ioHttpRedirectPolicyErrors() +{ + QFETCH(const QNetworkRequest::RedirectsPolicy, policy); + // This should never happen: + QVERIFY(policy != QNetworkRequest::ManualRedirectsPolicy); + + QFETCH(const bool, ssl); + QFETCH(const QString, location); + QFETCH(const int, maxRedirects); + QFETCH(const QNetworkReply::NetworkError, expectedError); + +#ifdef QT_NO_SSL + if (ssl || location.contains("https")) + QSKIP("SSL required to run this test"); +#endif + + // Setup the server. + MiniHttpServer server("", ssl); + server.setDataToTransmit(tempRedirectReplyStr().arg(location.arg(server.serverPort())).toLatin1()); + + QUrl url(QLatin1String( +#ifndef QT_NO_SSL + ssl ? "https://localhost" : +#endif + "http://localhost")); + url.setPort(server.serverPort()); + + QNetworkRequest request(url); + request.setMaximumRedirectsAllowed(maxRedirects); + // We always reset the policy to the default one ('Manual') after any related + // test is finished: + QCOMPARE(manager.redirectsPolicy(), QNetworkRequest::ManualRedirectsPolicy); + manager.setRedirectsPolicy(policy); + QCOMPARE(manager.redirectsPolicy(), policy); + + QNetworkReplyPtr reply(manager.get(request)); + // Set it back to default: + manager.setRedirectsPolicy(QNetworkRequest::ManualRedirectsPolicy); + + if (ssl) + reply->ignoreSslErrors(); + + QSignalSpy spy(reply.data(), SIGNAL(error(QNetworkReply::NetworkError))); + + QCOMPARE(waitForFinish(reply), int(Failure)); + QCOMPARE(spy.count(), 1); + QCOMPARE(reply->error(), expectedError); +} + #ifndef QT_NO_SSL class PutWithServerClosingConnectionImmediatelyHandler: public QObject -- cgit v1.2.3