Add a user-controlled auto-redirect policy
With this new policy, after emitting 'redirected', QNetworkReplyHttpImpl waits for client code to decide if QNAM should follow this redirect or not. The client can either allow this redirect by emitting 'redirectAllowed' or abort the reply. Task-number: QTPM-236 Change-Id: Ia04619f6bd1f0caa477833ae859b24033027b2e1 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
45f80a52c2
commit
231259c3d5
|
|
@ -567,6 +567,8 @@ QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socke
|
|||
return QUrl();
|
||||
}
|
||||
break;
|
||||
case QNetworkRequest::UserVerifiedRedirectsPolicy:
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(!"Unexpected redirect policy");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,6 +304,20 @@ QNetworkReplyPrivate::QNetworkReplyPrivate()
|
|||
\sa QNetworkRequest::FollowRedirectsAttribute
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn void QNetworkReply::redirectAllowed()
|
||||
\since 5.9
|
||||
|
||||
When client code handling the redirected() signal has verified the new URL,
|
||||
it emits this signal to allow the redirect to go ahead. This protocol applies
|
||||
to network requests whose redirects policy is set to
|
||||
QNetworkRequest::UserVerifiedRedirectsPolicy.
|
||||
|
||||
\sa QNetworkRequest::UserVerifiedRedirectsPolicy
|
||||
QNetworkAccessManager::setRedirectsPolicy(),
|
||||
QNetworkRequest::RedirectsPolicyAttribute
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn void QNetworkReply::metaDataChanged()
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ Q_SIGNALS:
|
|||
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
|
||||
#endif
|
||||
void redirected(const QUrl &url);
|
||||
void redirectAllowed();
|
||||
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
|
|
|
|||
|
|
@ -824,12 +824,16 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
|
|||
QObject::connect(delegate, SIGNAL(redirected(QUrl,int,int)),
|
||||
q, SLOT(onRedirected(QUrl,int,int)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
QObject::connect(q, SIGNAL(redirectAllowed()), q, SLOT(followRedirect()),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
#ifndef QT_NO_SSL
|
||||
QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
|
||||
q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
|
||||
Qt::QueuedConnection);
|
||||
#endif
|
||||
// Those need to report back, therefire BlockingQueuedConnection
|
||||
// Those need to report back, therefore BlockingQueuedConnection
|
||||
QObject::connect(delegate, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
|
||||
q, SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
|
||||
Qt::BlockingQueuedConnection);
|
||||
|
|
@ -1121,19 +1125,26 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
|
|||
if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
|
||||
url = redirectUrl;
|
||||
|
||||
QNetworkRequest redirectRequest = createRedirectRequest(originalRequest, redirectUrl, maxRedirectsRemaining);
|
||||
redirectRequest = createRedirectRequest(originalRequest, redirectUrl, maxRedirectsRemaining);
|
||||
operation = getRedirectOperation(operation, httpStatus);
|
||||
|
||||
if (httpRequest.redirectsPolicy() != QNetworkRequest::UserVerifiedRedirectsPolicy)
|
||||
followRedirect();
|
||||
|
||||
emit q->redirected(redirectUrl);
|
||||
}
|
||||
|
||||
void QNetworkReplyHttpImplPrivate::followRedirect()
|
||||
{
|
||||
Q_Q(QNetworkReplyHttpImpl);
|
||||
|
||||
cookedHeaders.clear();
|
||||
|
||||
if (managerPrivate->thread)
|
||||
managerPrivate->thread->disconnect();
|
||||
|
||||
// Recurse
|
||||
QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection,
|
||||
Q_ARG(QNetworkRequest, redirectRequest));
|
||||
|
||||
emit q->redirected(redirectUrl);
|
||||
}
|
||||
|
||||
void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ public:
|
|||
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))
|
||||
Q_PRIVATE_SLOT(d_func(), void followRedirect())
|
||||
|
||||
#ifndef QT_NO_SSL
|
||||
protected:
|
||||
|
|
@ -212,6 +213,7 @@ public:
|
|||
QSharedPointer<QRingBuffer> outgoingDataBuffer;
|
||||
void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); // dup?
|
||||
void onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemainig);
|
||||
void followRedirect();
|
||||
qint64 bytesUploaded;
|
||||
|
||||
|
||||
|
|
@ -263,6 +265,7 @@ public:
|
|||
QList<QSslError> pendingIgnoreSslErrorsList;
|
||||
#endif
|
||||
|
||||
QNetworkRequest redirectRequest;
|
||||
|
||||
bool loadFromCacheIfAllowed(QHttpNetworkRequest &httpRequest);
|
||||
void invalidateCache();
|
||||
|
|
|
|||
|
|
@ -360,6 +360,16 @@ QT_BEGIN_NAMESPACE
|
|||
Note, http://example.com and http://example.com:80
|
||||
will fail with this policy (implicit/explicit ports
|
||||
are considered to be a mismatch).
|
||||
|
||||
\value UserVerifiedRedirectsPolicy Client decides whether to follow each
|
||||
redirect by handling the redirected()
|
||||
signal, emitting redirectAllowed() on
|
||||
the QNetworkReply object to allow
|
||||
the redirect or aborting/finishing it to
|
||||
reject the redirect. This can be used,
|
||||
for example, to ask the user whether to
|
||||
accept the redirect, or to decide
|
||||
based on some app-specific configuration.
|
||||
*/
|
||||
|
||||
class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ public:
|
|||
enum RedirectsPolicy {
|
||||
ManualRedirectsPolicy,
|
||||
NoLessSafeRedirectsPolicy,
|
||||
SameOriginRedirectsPolicy
|
||||
SameOriginRedirectsPolicy,
|
||||
UserVerifiedRedirectsPolicy
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ class tst_QNetworkReply: public QObject
|
|||
return s;
|
||||
}
|
||||
|
||||
static const QByteArray httpEmpty200Response;
|
||||
|
||||
QEventLoop *loop;
|
||||
enum RunSimpleRequestReturn { Timeout = 0, Success, Failure };
|
||||
int returnCode;
|
||||
|
|
@ -480,6 +482,8 @@ private Q_SLOTS:
|
|||
void ioHttpRedirectPolicy();
|
||||
void ioHttpRedirectPolicyErrors_data();
|
||||
void ioHttpRedirectPolicyErrors();
|
||||
void ioHttpUserVerifiedRedirect_data();
|
||||
void ioHttpUserVerifiedRedirect();
|
||||
#ifndef QT_NO_SSL
|
||||
void putWithServerClosingConnectionImmediately();
|
||||
#endif
|
||||
|
|
@ -493,6 +497,8 @@ private:
|
|||
bool notEnoughDataForFastSender;
|
||||
};
|
||||
|
||||
const QByteArray tst_QNetworkReply::httpEmpty200Response =
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
|
||||
bool tst_QNetworkReply::seedCreated = false;
|
||||
|
||||
#define RUN_REQUEST(call) \
|
||||
|
|
@ -8034,10 +8040,9 @@ void tst_QNetworkReply::putWithRateLimiting()
|
|||
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);
|
||||
MiniHttpServer server2(httpEmpty200Response);
|
||||
|
||||
QUrl redirectUrl = QUrl(localhost);
|
||||
redirectUrl.setPort(server2.serverPort());
|
||||
|
|
@ -8079,11 +8084,9 @@ 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);
|
||||
MiniHttpServer server3(httpEmpty200Response);
|
||||
|
||||
QUrl server2Url(localhost);
|
||||
server2Url.setPort(server2.serverPort());
|
||||
|
|
@ -8227,7 +8230,7 @@ void tst_QNetworkReply::ioHttpRedirectPolicy()
|
|||
"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(httpEmpty200Response);
|
||||
redirectServer.responses.push_back(tempRedirectReplyStr().arg(QString(url.toEncoded())).toLatin1());
|
||||
|
||||
// This is the default one we preserve between tests.
|
||||
|
|
@ -8332,6 +8335,56 @@ void tst_QNetworkReply::ioHttpRedirectPolicyErrors()
|
|||
QCOMPARE(reply->error(), expectedError);
|
||||
}
|
||||
|
||||
void tst_QNetworkReply::ioHttpUserVerifiedRedirect_data()
|
||||
{
|
||||
QTest::addColumn<bool>("followRedirect");
|
||||
QTest::addColumn<int>("statusCode");
|
||||
|
||||
QTest::newRow("allow-redirect") << true << 200;
|
||||
QTest::newRow("reject-redirect") << false << 307;
|
||||
}
|
||||
|
||||
void tst_QNetworkReply::ioHttpUserVerifiedRedirect()
|
||||
{
|
||||
QFETCH(const bool, followRedirect);
|
||||
QFETCH(const int, statusCode);
|
||||
|
||||
// Setup HTTP server.
|
||||
MiniHttpServer target(httpEmpty200Response, false);
|
||||
QUrl url("http://localhost");
|
||||
url.setPort(target.serverPort());
|
||||
|
||||
MiniHttpServer redirectServer("", false);
|
||||
redirectServer.setDataToTransmit(tempRedirectReplyStr().arg(QString(url.toEncoded())).toLatin1());
|
||||
url.setPort(redirectServer.serverPort());
|
||||
|
||||
QCOMPARE(manager.redirectsPolicy(), QNetworkRequest::ManualRedirectsPolicy);
|
||||
manager.setRedirectsPolicy(QNetworkRequest::UserVerifiedRedirectsPolicy);
|
||||
QCOMPARE(manager.redirectsPolicy(), QNetworkRequest::UserVerifiedRedirectsPolicy);
|
||||
|
||||
QNetworkReplyPtr reply(manager.get(QNetworkRequest(url)));
|
||||
reply->connect(reply.data(), &QNetworkReply::redirected,
|
||||
[&](const QUrl &redirectUrl) {
|
||||
qDebug() << "redirect to:" << redirectUrl;
|
||||
if (followRedirect) {
|
||||
qDebug() << "confirmed.";
|
||||
emit reply->redirectAllowed();
|
||||
} else{
|
||||
qDebug() << "rejected.";
|
||||
emit reply->abort();
|
||||
}
|
||||
});
|
||||
|
||||
// Before any test failed, reset the policy to default:
|
||||
manager.setRedirectsPolicy(QNetworkRequest::ManualRedirectsPolicy);
|
||||
QCOMPARE(manager.redirectsPolicy(), QNetworkRequest::ManualRedirectsPolicy);
|
||||
|
||||
QSignalSpy finishedSpy(reply.data(), SIGNAL(finished()));
|
||||
waitForFinish(reply);
|
||||
QCOMPARE(finishedSpy.count(), 1);
|
||||
QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), statusCode);
|
||||
}
|
||||
|
||||
#ifndef QT_NO_SSL
|
||||
|
||||
class PutWithServerClosingConnectionImmediatelyHandler: public QObject
|
||||
|
|
|
|||
Loading…
Reference in New Issue