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>bb10
parent
f7118e7f1b
commit
d815de8c26
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -275,6 +283,19 @@ QNetworkReplyPrivate::QNetworkReplyPrivate()
|
|||
\sa QSslPreSharedKeyAuthenticator
|
||||
*/
|
||||
|
||||
/*!
|
||||
\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()
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public:
|
|||
|
||||
QNetworkReplyPrivate();
|
||||
QNetworkRequest request;
|
||||
QNetworkRequest originalRequest;
|
||||
QUrl url;
|
||||
QPointer<QNetworkAccessManager> manager;
|
||||
qint64 readBufferMaxSize;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue