Http: Fix POST-to-GET redirects still uploading or transmitting CL
CL = Content-Length The uploadByteDevice was kept after a redirect which caused the internals to assume that we had to upload the data. Even if this was not the case we still transmitted the Content-Length header from the first request which was now stored in two places. Fixes: QTBUG-84162 Pick-to: 5.15 Change-Id: Ic86b1ef0766ffcc50beeed96c1c915b721d40209 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
76228da096
commit
306ebe03ea
@ -117,6 +117,11 @@ void QHttpNetworkHeaderPrivate::prependHeaderField(const QByteArray &name, const
|
|||||||
fields.prepend(qMakePair(name, data));
|
fields.prepend(qMakePair(name, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QHttpNetworkHeaderPrivate::clearHeaders()
|
||||||
|
{
|
||||||
|
fields.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const
|
bool QHttpNetworkHeaderPrivate::operator==(const QHttpNetworkHeaderPrivate &other) const
|
||||||
{
|
{
|
||||||
return (url == other.url);
|
return (url == other.url);
|
||||||
|
@ -93,6 +93,7 @@ public:
|
|||||||
QList<QByteArray> headerFieldValues(const QByteArray &name) const;
|
QList<QByteArray> headerFieldValues(const QByteArray &name) const;
|
||||||
void setHeaderField(const QByteArray &name, const QByteArray &data);
|
void setHeaderField(const QByteArray &name, const QByteArray &data);
|
||||||
void prependHeaderField(const QByteArray &name, const QByteArray &data);
|
void prependHeaderField(const QByteArray &name, const QByteArray &data);
|
||||||
|
void clearHeaders();
|
||||||
bool operator==(const QHttpNetworkHeaderPrivate &other) const;
|
bool operator==(const QHttpNetworkHeaderPrivate &other) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -286,6 +286,11 @@ void QHttpNetworkRequest::prependHeaderField(const QByteArray &name, const QByte
|
|||||||
d->prependHeaderField(name, data);
|
d->prependHeaderField(name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QHttpNetworkRequest::clearHeaders()
|
||||||
|
{
|
||||||
|
d->clearHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
QHttpNetworkRequest &QHttpNetworkRequest::operator=(const QHttpNetworkRequest &other)
|
QHttpNetworkRequest &QHttpNetworkRequest::operator=(const QHttpNetworkRequest &other)
|
||||||
{
|
{
|
||||||
d = other.d;
|
d = other.d;
|
||||||
|
@ -103,6 +103,7 @@ public:
|
|||||||
QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const override;
|
QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const override;
|
||||||
void setHeaderField(const QByteArray &name, const QByteArray &data) override;
|
void setHeaderField(const QByteArray &name, const QByteArray &data) override;
|
||||||
void prependHeaderField(const QByteArray &name, const QByteArray &data);
|
void prependHeaderField(const QByteArray &name, const QByteArray &data);
|
||||||
|
void clearHeaders();
|
||||||
|
|
||||||
Operation operation() const;
|
Operation operation() const;
|
||||||
void setOperation(Operation operation);
|
void setOperation(Operation operation);
|
||||||
|
@ -1155,6 +1155,26 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
|
|||||||
redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
|
redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
|
||||||
operation = getRedirectOperation(operation, httpStatus);
|
operation = getRedirectOperation(operation, httpStatus);
|
||||||
|
|
||||||
|
// Clear stale headers, the relevant ones get set again later
|
||||||
|
httpRequest.clearHeaders();
|
||||||
|
if (operation == QNetworkAccessManager::GetOperation
|
||||||
|
|| operation == QNetworkAccessManager::HeadOperation) {
|
||||||
|
// possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
|
||||||
|
uploadByteDevice.reset();
|
||||||
|
uploadByteDevicePosition = 0;
|
||||||
|
if (outgoingData) {
|
||||||
|
QObject::disconnect(outgoingData, SIGNAL(readyRead()), q,
|
||||||
|
SLOT(_q_bufferOutgoingData()));
|
||||||
|
QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q,
|
||||||
|
SLOT(_q_bufferOutgoingDataFinished()));
|
||||||
|
}
|
||||||
|
outgoingData = nullptr;
|
||||||
|
outgoingDataBuffer.reset();
|
||||||
|
// We need to explicitly unset these headers so they're not reapplied to the httpRequest
|
||||||
|
redirectRequest.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
|
||||||
|
redirectRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
|
if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
|
||||||
auto cookies = cookieJar->cookiesForUrl(url);
|
auto cookies = cookieJar->cookiesForUrl(url);
|
||||||
if (!cookies.empty()) {
|
if (!cookies.empty()) {
|
||||||
@ -1435,6 +1455,9 @@ void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
|
|||||||
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
|
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
|
||||||
void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
|
void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
|
||||||
{
|
{
|
||||||
|
if (!uploadByteDevice) // uploadByteDevice is no longer available
|
||||||
|
return;
|
||||||
|
|
||||||
if (uploadByteDevicePosition + amount != pos) {
|
if (uploadByteDevicePosition + amount != pos) {
|
||||||
// Sanity check, should not happen.
|
// Sanity check, should not happen.
|
||||||
error(QNetworkReply::UnknownNetworkError, QString());
|
error(QNetworkReply::UnknownNetworkError, QString());
|
||||||
@ -1449,6 +1472,9 @@ void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
|
|||||||
{
|
{
|
||||||
Q_Q(QNetworkReplyHttpImpl);
|
Q_Q(QNetworkReplyHttpImpl);
|
||||||
|
|
||||||
|
if (!uploadByteDevice) // uploadByteDevice is no longer available
|
||||||
|
return;
|
||||||
|
|
||||||
// call readPointer
|
// call readPointer
|
||||||
qint64 currentUploadDataLength = 0;
|
qint64 currentUploadDataLength = 0;
|
||||||
char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
|
char *data = const_cast<char*>(uploadByteDevice->readPointer(maxSize, currentUploadDataLength));
|
||||||
|
@ -492,6 +492,8 @@ private Q_SLOTS:
|
|||||||
void ioHttpRedirectMultipartPost();
|
void ioHttpRedirectMultipartPost();
|
||||||
void ioHttpRedirectDelete();
|
void ioHttpRedirectDelete();
|
||||||
void ioHttpRedirectCustom();
|
void ioHttpRedirectCustom();
|
||||||
|
void ioHttpRedirectWithUploadDevice_data();
|
||||||
|
void ioHttpRedirectWithUploadDevice();
|
||||||
#ifndef QT_NO_SSL
|
#ifndef QT_NO_SSL
|
||||||
void putWithServerClosingConnectionImmediately();
|
void putWithServerClosingConnectionImmediately();
|
||||||
#endif
|
#endif
|
||||||
@ -715,7 +717,8 @@ public slots:
|
|||||||
|
|
||||||
if (doubleEndlPos != -1) {
|
if (doubleEndlPos != -1) {
|
||||||
const int endOfHeader = doubleEndlPos + 4;
|
const int endOfHeader = doubleEndlPos + 4;
|
||||||
hasContent = receivedData.startsWith("POST") || receivedData.startsWith("PUT");
|
hasContent = receivedData.startsWith("POST") || receivedData.startsWith("PUT")
|
||||||
|
|| receivedData.startsWith("CUSTOM_WITH_PAYLOAD");
|
||||||
if (hasContent && contentLength == 0)
|
if (hasContent && contentLength == 0)
|
||||||
parseContentLength();
|
parseContentLength();
|
||||||
contentRead = receivedData.length() - endOfHeader;
|
contentRead = receivedData.length() - endOfHeader;
|
||||||
@ -8780,6 +8783,75 @@ void tst_QNetworkReply::ioHttpRedirectCustom()
|
|||||||
QVERIFY2(target.receivedData.startsWith("CUSTOM"), "Target server called with the wrong method");
|
QVERIFY2(target.receivedData.startsWith("CUSTOM"), "Target server called with the wrong method");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QNetworkReply::ioHttpRedirectWithUploadDevice_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QByteArray>("method");
|
||||||
|
QTest::addColumn<QString>("status");
|
||||||
|
QTest::addColumn<bool>("keepMethod");
|
||||||
|
|
||||||
|
QTest::addRow("post-301") << QByteArray("POST") << "301 Moved Permanently" << false;
|
||||||
|
QTest::addRow("post-307") << QByteArray("POST") << "307 Temporary Redirect" << true;
|
||||||
|
|
||||||
|
const QByteArray customVerb = QByteArray("CUSTOM_WITH_PAYLOAD");
|
||||||
|
QTest::addRow("custom-301") << customVerb << "301 Moved Permanently" << false;
|
||||||
|
QTest::addRow("custom-307") << customVerb << "307 Temporary Redirect" << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tests that we properly disregard the upload device when redirecting
|
||||||
|
and changing method to GET, and that it gets reset properly when
|
||||||
|
redirecting (without changing method) for POST and a custom method.
|
||||||
|
*/
|
||||||
|
void tst_QNetworkReply::ioHttpRedirectWithUploadDevice()
|
||||||
|
{
|
||||||
|
QFETCH(QByteArray, method);
|
||||||
|
QFETCH(QString, status);
|
||||||
|
QFETCH(bool, keepMethod);
|
||||||
|
|
||||||
|
MiniHttpServer target(httpEmpty200Response, false);
|
||||||
|
QUrl targetUrl("http://localhost/");
|
||||||
|
targetUrl.setPort(target.serverPort());
|
||||||
|
|
||||||
|
QString redirectReply = QStringLiteral("HTTP/1.1 %1\r\n"
|
||||||
|
"Content-Type: text/plain\r\n"
|
||||||
|
"location: %2\r\n"
|
||||||
|
"\r\n").arg(status, targetUrl.toString());
|
||||||
|
MiniHttpServer redirectServer(redirectReply.toLatin1());
|
||||||
|
QUrl url("http://localhost/");
|
||||||
|
url.setPort(redirectServer.serverPort());
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("text/plain"));
|
||||||
|
auto oldRedirectPolicy = manager.redirectPolicy();
|
||||||
|
manager.setRedirectPolicy(QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy);
|
||||||
|
|
||||||
|
QByteArray data = "Hello world";
|
||||||
|
QBuffer buffer(&data);
|
||||||
|
|
||||||
|
QNetworkReplyPtr reply;
|
||||||
|
if (method == "POST")
|
||||||
|
reply.reset(manager.post(request, &buffer));
|
||||||
|
else
|
||||||
|
reply.reset(manager.sendCustomRequest(request, method, &buffer));
|
||||||
|
// Restore previous policy:
|
||||||
|
manager.setRedirectPolicy(oldRedirectPolicy);
|
||||||
|
|
||||||
|
QCOMPARE(waitForFinish(reply), int(Success));
|
||||||
|
QByteArray expectedMethod = method;
|
||||||
|
if (!keepMethod)
|
||||||
|
expectedMethod = "GET";
|
||||||
|
QVERIFY2(target.receivedData.startsWith(expectedMethod), "Target server called with the wrong method");
|
||||||
|
if (keepMethod) { // keepMethod also means we resend the data
|
||||||
|
QVERIFY2(target.receivedData.endsWith(data), "Target server didn't receive the data");
|
||||||
|
} else {
|
||||||
|
// we shouldn't send Content-Length with not content (esp. for GET)
|
||||||
|
QVERIFY2(!target.receivedData.contains("Content-Length"),
|
||||||
|
"Target server should not have received a Content-Length header");
|
||||||
|
QVERIFY2(!target.receivedData.contains("Content-Type"),
|
||||||
|
"Target server should not have received a Content-Type header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef QT_NO_SSL
|
#ifndef QT_NO_SSL
|
||||||
|
|
||||||
class PutWithServerClosingConnectionImmediatelyHandler: public QObject
|
class PutWithServerClosingConnectionImmediatelyHandler: public QObject
|
||||||
|
Loading…
Reference in New Issue
Block a user