summaryrefslogtreecommitdiffstats
path: root/src/network/access/qnetworkreplyhttpimpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access/qnetworkreplyhttpimpl.cpp')
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp195
1 files changed, 153 insertions, 42 deletions
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index c445677792..8c0c098147 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -49,6 +49,8 @@
#include "qthread.h"
#include "QtCore/qcoreapplication.h"
+#include <QtCore/private/qthread_p.h>
+
#include "qnetworkcookiejar.h"
#ifndef QT_NO_HTTP
@@ -166,6 +168,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();
@@ -483,7 +486,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
@@ -597,7 +600,7 @@ QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(const QNetwo
}
}
-void QNetworkReplyHttpImplPrivate::postRequest()
+void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpRequest)
{
Q_Q(QNetworkReplyHttpImpl);
@@ -621,8 +624,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")
@@ -638,7 +642,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
@@ -669,9 +673,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:
@@ -705,7 +711,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;
@@ -717,7 +723,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
@@ -725,7 +731,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('-');
@@ -742,16 +748,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);
@@ -778,7 +784,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?
@@ -790,7 +796,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 {
@@ -825,6 +831,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)),
@@ -951,7 +960,7 @@ void QNetworkReplyHttpImplPrivate::invalidateCache()
{
QAbstractNetworkCache *nc = managerPrivate->networkCache;
if (nc)
- nc->remove(request.url());
+ nc->remove(httpRequest.url());
}
void QNetworkReplyHttpImplPrivate::initCacheSaveDevice()
@@ -1031,21 +1040,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).
@@ -1067,6 +1085,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);
@@ -1115,7 +1191,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
@@ -1129,12 +1214,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;
@@ -1156,7 +1242,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);
@@ -1203,6 +1289,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadProgressSlot(qint64 bytesReceive
// FIXME where is it closed?
}
+ if (isHttpRedirectResponse())
+ return;
+
bytesDownloaded = bytesReceived;
downloadBufferCurrentSize = bytesReceived;
@@ -1361,6 +1450,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);
@@ -1368,10 +1459,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()));
@@ -1388,6 +1485,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;
@@ -1583,13 +1688,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
}
@@ -1599,7 +1704,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;
}
@@ -1608,13 +1713,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;
}
}
@@ -1646,7 +1751,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
@@ -1708,18 +1813,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());
}
@@ -1742,7 +1849,6 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
cacheLoadDevice = 0;
QMetaObject::invokeMethod(q, "_q_finished", Qt::QueuedConnection);
}
-
}
@@ -1962,6 +2068,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);
@@ -1974,10 +2089,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();
}