193 changes: 151 additions & 42 deletions src/network/access/qnetworkreplyhttpimpl.cpp

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions src/network/access/qnetworkreplyhttpimpl_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class QNetworkReplyHttpImpl: public QNetworkReply

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())
Expand Down Expand Up @@ -126,7 +127,7 @@ class QNetworkReplyHttpImpl: public QNetworkReply
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:
Expand Down Expand Up @@ -157,7 +158,7 @@ class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
QNetworkReplyHttpImplPrivate();
~QNetworkReplyHttpImplPrivate();

bool start();
bool start(const QNetworkRequest &newHttpRequest);
void _q_startOperation();

void _q_cacheLoadReadyRead();
Expand Down Expand Up @@ -200,6 +201,7 @@ class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
QIODevice *outgoingData;
QSharedPointer<QRingBuffer> outgoingDataBuffer;
void emitReplyUploadProgress(qint64 bytesSent, qint64 bytesTotal); // dup?
void onRedirected(const QUrl &redirectUrl, int httpStatus, int maxRedirectsRemainig);
qint64 bytesUploaded;


Expand Down Expand Up @@ -259,9 +261,10 @@ class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
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:
Expand Down
44 changes: 41 additions & 3 deletions src/network/access/qnetworkrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
{
Expand All @@ -325,7 +335,7 @@ class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
{
url = other.url;
priority = other.priority;

maxRedirectsAllowed = other.maxRedirectsAllowed;
#ifndef QT_NO_SSL
sslConfiguration = 0;
if (other.sslConfiguration)
Expand All @@ -338,7 +348,8 @@ class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
return url == other.url &&
priority == other.priority &&
rawHeaders == other.rawHeaders &&
attributes == other.attributes;
attributes == other.attributes &&
maxRedirectsAllowed == other.maxRedirectsAllowed;
// don't compare cookedHeaders
}

Expand All @@ -347,6 +358,7 @@ class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
#ifndef QT_NO_SSL
mutable QSslConfiguration *sslConfiguration;
#endif
int maxRedirectsAllowed;
};

/*!
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions src/network/access/qnetworkrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Q_NETWORK_EXPORT QNetworkRequest
SpdyAllowedAttribute,
SpdyWasUsedAttribute,
EmitAllUploadProgressSignalsAttribute,
FollowRedirectsAttribute,

User = 1000,
UserMax = 32767
Expand Down Expand Up @@ -141,6 +142,10 @@ class Q_NETWORK_EXPORT QNetworkRequest
Priority priority() const;
void setPriority(Priority priority);

// HTTP redirect related
int maximumRedirectsAllowed() const;
void setMaximumRedirectsAllowed(int maximumRedirectsAllowed);

private:
QSharedDataPointer<QNetworkRequestPrivate> d;
friend class QNetworkRequestPrivate;
Expand Down
158 changes: 156 additions & 2 deletions tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -595,10 +608,16 @@ class MiniHttpServer: public QTcpServer
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()
Expand Down Expand Up @@ -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()
Expand Down