summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMate Barany <mate.barany@qt.io>2023-07-26 15:35:25 +0200
committerMate Barany <mate.barany@qt.io>2023-10-17 21:23:42 +0200
commit0345f07dd11cb931e4c3625284bb1b408455fb32 (patch)
tree206b5f124896e3284b528f5ce789950b86600c5f
parent1097c21748362e2a53f2b050c170af5b7cb063b0 (diff)
Support body for an HTTP GET request
Add two overloads to QNetworkAccessManager to support GET requests with body. Modify QNetworkReplyHttpImpl as well as these requests should not be cached. If the request is redirected it is possible that its type changes from POST/PUT to GET and in this case the message body is deleted. However, if a GET request has a body it should keep it after it has been redirected - modify QNetworkReplyHttpImpl to keep the message body after it has been redirected. Fixes: QTBUG-112871 Change-Id: Ib01898638ed94238a98291870a5c51d56030868a Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp40
-rw-r--r--src/network/access/qnetworkaccessmanager.h2
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp16
-rw-r--r--tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp293
4 files changed, 342 insertions, 9 deletions
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 1c604a01ec..4ca51fb98f 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -780,6 +780,46 @@ QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
}
/*!
+ \since 6.7
+
+ \overload
+
+ \note A GET request with a message body is not cached.
+
+ \note If the request is redirected, the message body will be kept only if the status code is
+ 307 or 308.
+*/
+
+QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request, QIODevice *data)
+{
+ QNetworkRequest newRequest(request);
+ return d_func()->postProcess(
+ createRequest(QNetworkAccessManager::GetOperation, newRequest, data));
+}
+
+/*!
+ \since 6.7
+
+ \overload
+
+ \note A GET request with a message body is not cached.
+
+ \note If the request is redirected, the message body will be kept only if the status code is
+ 307 or 308.
+*/
+
+QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request, const QByteArray &data)
+{
+ QBuffer *buffer = new QBuffer;
+ buffer->setData(data);
+ buffer->open(QIODevice::ReadOnly);
+
+ QNetworkReply *reply = get(request, buffer);
+ buffer->setParent(reply);
+ return reply;
+}
+
+/*!
Sends an HTTP POST request to the destination specified by \a request
and returns a new QNetworkReply object opened for reading that will
contain the reply sent by the server. The contents of the \a data
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index f0c99c03b8..5704061f15 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -80,6 +80,8 @@ public:
QNetworkReply *head(const QNetworkRequest &request);
QNetworkReply *get(const QNetworkRequest &request);
+ QNetworkReply *get(const QNetworkRequest &request, QIODevice *data);
+ QNetworkReply *get(const QNetworkRequest &request, const QByteArray &data);
QNetworkReply *post(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index 30fa1a1e52..70533a229e 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -699,8 +699,13 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
switch (operation) {
case QNetworkAccessManager::GetOperation:
httpRequest.setOperation(QHttpNetworkRequest::Get);
- if (loadFromCacheIfAllowed(httpRequest))
+ // If the request has a body, createUploadByteDevice() and don't use caching
+ if (outgoingData) {
+ invalidateCache();
+ createUploadByteDevice();
+ } else if (loadFromCacheIfAllowed(httpRequest)) {
return; // no need to send the request! :)
+ }
break;
case QNetworkAccessManager::HeadOperation:
@@ -1239,13 +1244,18 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
return;
}
+ // If the original operation was a GET with a body and the status code is either
+ // 307 or 308 then keep the message body
+ const bool getOperationKeepsBody = (operation == QNetworkAccessManager::GetOperation)
+ && (httpStatus == 307 || httpStatus == 308);
+
redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
operation = getRedirectOperation(operation, httpStatus);
// Clear stale headers, the relevant ones get set again later
httpRequest.clearHeaders();
- if (operation == QNetworkAccessManager::GetOperation
- || operation == QNetworkAccessManager::HeadOperation) {
+ if ((operation == QNetworkAccessManager::GetOperation
+ || operation == QNetworkAccessManager::HeadOperation) && !getOperationKeepsBody) {
// possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
uploadByteDevice.reset();
uploadByteDevicePosition = 0;
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
index ec8b341efa..c169e77cac 100644
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -133,6 +133,29 @@ class tst_QNetworkReply: public QObject
"\r\n";
return s;
}
+ static QString movedReplyStr() {
+ QString s = "HTTP/1.1 301 Moved Permanently\r\n"
+ "Content-Type: text/plain\r\n"
+ "location: %1\r\n"
+ "\r\n";
+ return s;
+ }
+
+ static QString foundReplyStr() {
+ QString s = "HTTP/1.1 302 Found\r\n"
+ "Content-Type: text/plain\r\n"
+ "location: %1\r\n"
+ "\r\n";
+ return s;
+ }
+
+ static QString permRedirectReplyStr() {
+ QString s = "HTTP/1.1 308 Permanent Redirect\r\n"
+ "Content-Type: text/plain\r\n"
+ "location: %1\r\n"
+ "\r\n";
+ return s;
+ }
static const QByteArray httpEmpty200Response;
static const QString filePermissionFileName;
@@ -216,6 +239,12 @@ private Q_SLOTS:
void getFromFtpAfterError(); // QTBUG-40797
void getFromHttp_data();
void getFromHttp();
+ void getWithBodyFromHttp_data();
+ void getWithBodyFromHttp();
+ void getWithAndWithoutBodyFromHttp_data();
+ void getWithAndWithoutBodyFromHttp();
+ void getWithBodyRedirected_data();
+ void getWithBodyRedirected();
void getErrors_data();
void getErrors();
#if QT_CONFIG(networkproxy)
@@ -613,7 +642,7 @@ public:
int totalConnections;
bool stopTransfer = false;
- bool hasContent = false;
+ bool checkedContentLength = false;
int contentRead = 0;
int contentLength = 0;
@@ -702,6 +731,9 @@ private:
void parseContentLength()
{
int index = receivedData.indexOf("Content-Length:");
+ if (index == -1)
+ return;
+
index += sizeof("Content-Length:") - 1;
const auto end = std::find(receivedData.cbegin() + index, receivedData.cend(), '\r');
auto num = receivedData.mid(index, std::distance(receivedData.cbegin() + index, end));
@@ -742,12 +774,14 @@ public slots:
if (doubleEndlPos != -1) {
const int endOfHeader = doubleEndlPos + 4;
- hasContent = receivedData.startsWith("POST") || receivedData.startsWith("PUT")
- || receivedData.startsWith("CUSTOM_WITH_PAYLOAD");
- if (hasContent && contentLength == 0)
- parseContentLength();
contentRead = receivedData.size() - endOfHeader;
- if (hasContent && contentRead < contentLength)
+
+ if (!checkedContentLength) {
+ parseContentLength();
+ checkedContentLength = true;
+ }
+
+ if (contentRead < contentLength)
return;
// multiple requests incoming. remove the bytes of the current one
@@ -1991,6 +2025,253 @@ void tst_QNetworkReply::getFromHttp()
QCOMPARE(reply->readAll(), reference.readAll());
}
+void tst_QNetworkReply::getWithBodyFromHttp_data()
+{
+ QTest::addColumn<QByteArray>("dataFromClientToServer");
+ QTest::addColumn<bool>("useDevice");
+ QTest::newRow("with-bytearray") << QByteArray("Body 1") << false;
+ QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false;
+ QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false;
+ QTest::newRow("with-device") << QByteArray("Body 1") << true;
+ QTest::newRow("with-device2") << QByteArray("Body 2") << true;
+ QTest::newRow("with-device3") << QByteArray("Body 3") << true;
+}
+
+void tst_QNetworkReply::getWithBodyFromHttp()
+{
+ QFETCH(QByteArray, dataFromClientToServer);
+ QFETCH(bool, useDevice);
+
+ QBuffer buff;
+ buff.setData(dataFromClientToServer);
+ buff.open(QIODevice::ReadOnly);
+
+ QByteArray dataFromServerToClient = QByteArray("Long first line\r\nLong second line");
+ QByteArray httpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: ");
+ httpResponse += QByteArray::number(dataFromServerToClient.size());
+ httpResponse += "\r\n\r\n";
+ httpResponse += dataFromServerToClient;
+
+ MiniHttpServer server(httpResponse);
+ server.doClose = true;
+
+ QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort())));
+ QNetworkReplyPtr reply;
+
+ if (useDevice)
+ reply.reset(manager.get(request, &buff));
+ else
+ reply.reset(manager.get(request, dataFromClientToServer));
+
+ QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply));
+ QCOMPARE(server.contentLength, dataFromClientToServer.size());
+ QCOMPARE(server.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer);
+ QByteArray content = reply->readAll();
+ QCOMPARE(content, dataFromServerToClient);
+}
+
+void tst_QNetworkReply::getWithAndWithoutBodyFromHttp_data()
+{
+ QTest::addColumn<QByteArray>("dataFromClientToServer");
+ QTest::addColumn<bool>("alwaysCache");
+ QTest::addColumn<tst_QNetworkReply::RunSimpleRequestReturn>("requestReturn");
+ QTest::addColumn<bool>("useDevice");
+ QTest::newRow("with-bytearray") << QByteArray("Body 1") << false << Success << false;
+ QTest::newRow("with-bytearray2") << QByteArray("Body 2") << false << Success << false;
+ QTest::newRow("with-bytearray3") << QByteArray("Body 3") << false << Success << false;
+ QTest::newRow("with-bytearray-cache") << QByteArray("Body 1") << true << Failure << false;
+ QTest::newRow("with-bytearray-cache2") << QByteArray("Body 2") << true << Failure << false;
+ QTest::newRow("with-bytearray-cache3") << QByteArray("Body 3") << true << Failure << false;
+ QTest::newRow("with-device") << QByteArray("Body 1") << false << Success << true;
+ QTest::newRow("with-device2") << QByteArray("Body 2") << false << Success << true;
+ QTest::newRow("with-device3") << QByteArray("Body 3") << false << Success << true;
+ QTest::newRow("with-device-cache") << QByteArray("Body 1") << true << Failure << true;
+ QTest::newRow("with-device-cache2") << QByteArray("Body 2") << true << Failure << true;
+ QTest::newRow("with-device-cache3") << QByteArray("Body 3") << true << Failure << true;
+}
+
+void tst_QNetworkReply::getWithAndWithoutBodyFromHttp()
+{
+ QFETCH(QByteArray, dataFromClientToServer);
+ QFETCH(bool, alwaysCache);
+ QFETCH(tst_QNetworkReply::RunSimpleRequestReturn, requestReturn);
+ QFETCH(bool, useDevice);
+
+ QBuffer buff;
+ buff.setData(dataFromClientToServer);
+ buff.open(QIODevice::ReadOnly);
+
+ QNetworkAccessManager qnam;
+ MyMemoryCache *memoryCache = new MyMemoryCache(&qnam);
+ qnam.setCache(memoryCache);
+
+ const int sizeOfDataFromServerToClient =3;
+ QByteArray dataFromServerToClient1 = QByteArray("aaa");
+ QByteArray dataFromServerToClient2 = QByteArray("bbb");
+ QByteArray dataFromServerToClient3 = QByteArray("ccc");
+
+ QByteArray baseHttpResponse = QByteArray("HTTP/1.0 200 OK\r\nContent-Length: ");
+ baseHttpResponse += QByteArray::number(sizeOfDataFromServerToClient);
+ baseHttpResponse += "\r\n\r\n";
+
+ MiniHttpServer server(baseHttpResponse + dataFromServerToClient1);
+ server.doClose = true;
+
+ QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort())));
+
+ // Send request without body
+ QNetworkReplyPtr reply(manager.get(request));
+ QVERIFY2(waitForFinish(reply) == Success, msgWaitForFinished(reply));
+ QByteArray content = reply->readAll();
+ QCOMPARE(content, dataFromServerToClient1);
+
+ if (alwaysCache) {
+ request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
+ QNetworkRequest::AlwaysCache);
+ }
+
+ server.dataToTransmit = baseHttpResponse + dataFromServerToClient2;
+
+ // Send request with body
+ QNetworkReplyPtr reply2;
+ if (useDevice)
+ reply2.reset(manager.get(request, &buff));
+ else
+ reply2.reset(manager.get(request, dataFromClientToServer));
+
+ QVERIFY2(waitForFinish(reply2) == requestReturn, msgWaitForFinished(reply2));
+ content = reply2->readAll();
+
+ if (alwaysCache)
+ QVERIFY(content.isEmpty());
+ else
+ QCOMPARE(content, dataFromServerToClient2);
+
+ QCOMPARE(reply2->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false);
+
+ if (alwaysCache) {
+ request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
+ QNetworkRequest::PreferNetwork);
+ }
+
+ server.dataToTransmit = baseHttpResponse + dataFromServerToClient3;
+
+ // Send another request without a body
+ QNetworkReplyPtr reply3(manager.get(request));
+ QVERIFY2(waitForFinish(reply3) == Success, msgWaitForFinished(reply3));
+ content = reply3->readAll();
+ QCOMPARE(content, dataFromServerToClient3);
+ QCOMPARE(reply3->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(), false);
+}
+
+void tst_QNetworkReply::getWithBodyRedirected_data()
+{
+ QTest::addColumn<QByteArray>("dataFromClientToServer");
+ QTest::addColumn<bool>("useDevice");
+ QTest::addColumn<int>("status");
+ QTest::newRow("with-bytearray - 301") << QByteArray("Body 1") << false << 301;
+ QTest::newRow("with-bytearray2 - 301") << QByteArray("Body 2") << false << 301;
+ QTest::newRow("with-bytearray3 - 301") << QByteArray("Body 3") << false << 301;
+ QTest::newRow("with-device - 301") << QByteArray("Body 1") << true << 301;
+ QTest::newRow("with-device2 - 301") << QByteArray("Body 2") << true << 301;
+ QTest::newRow("with-device3 - 301") << QByteArray("Body 3") << true << 301;
+ QTest::newRow("with-bytearray - 302") << QByteArray("Body 1") << false << 302;
+ QTest::newRow("with-bytearray2 - 302") << QByteArray("Body 2") << false << 302;
+ QTest::newRow("with-bytearray3 - 302") << QByteArray("Body 3") << false << 302;
+ QTest::newRow("with-device - 302") << QByteArray("Body 1") << true << 302;
+ QTest::newRow("with-device2 - 302") << QByteArray("Body 2") << true << 302;
+ QTest::newRow("with-device3 - 302") << QByteArray("Body 3") << true << 302;
+ QTest::newRow("with-bytearray - 307") << QByteArray("Body 1") << false << 307;
+ QTest::newRow("with-bytearray2 - 307") << QByteArray("Body 2") << false << 307;
+ QTest::newRow("with-bytearray3 - 307") << QByteArray("Body 3") << false << 307;
+ QTest::newRow("with-device - 307") << QByteArray("Body 1") << true << 307;
+ QTest::newRow("with-device2 - 307") << QByteArray("Body 2") << true << 307;
+ QTest::newRow("with-device3 - 307") << QByteArray("Body 3") << true << 307;
+ QTest::newRow("with-bytearray - 308") << QByteArray("Body 1") << false << 308;
+ QTest::newRow("with-bytearray2 - 308") << QByteArray("Body 2") << false << 308;
+ QTest::newRow("with-bytearray3 - 308") << QByteArray("Body 3") << false << 308;
+ QTest::newRow("with-device - 308") << QByteArray("Body 1") << true << 308;
+ QTest::newRow("with-device2 - 308") << QByteArray("Body 2") << true << 308;
+ QTest::newRow("with-device3 - 308") << QByteArray("Body 3") << true << 308;
+}
+
+void tst_QNetworkReply::getWithBodyRedirected()
+{
+ QFETCH(QByteArray, dataFromClientToServer);
+ QFETCH(bool, useDevice);
+ QFETCH(int, status);
+
+ QBuffer buff;
+ buff.setData(dataFromClientToServer);
+ buff.open(QIODevice::ReadOnly);
+
+ QUrl localhost = QUrl("http://localhost");
+
+ // Setup server to which the second server will redirect to
+ MiniHttpServer server2(httpEmpty200Response);
+
+ QUrl redirectUrl = QUrl(localhost);
+ redirectUrl.setPort(server2.serverPort());
+
+ QByteArray redirectReply;
+ switch (status) {
+ case 301: redirectReply =
+ foundReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break;
+ case 302: redirectReply =
+ movedReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break;
+ case 307: redirectReply =
+ tempRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break;
+ case 308: redirectReply =
+ permRedirectReplyStr().arg(QString(redirectUrl.toEncoded())).toLatin1(); break;
+ default: QFAIL("Unexpected status code"); break;
+ }
+
+ // Setup redirect server
+ MiniHttpServer server(redirectReply);
+
+ localhost.setPort(server.serverPort());
+ QNetworkRequest request(localhost);
+ request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
+ QNetworkRequest::NoLessSafeRedirectPolicy);
+
+ QNetworkReplyPtr reply;
+ if (useDevice)
+ reply.reset(manager.get(request, &buff));
+ else
+ reply.reset(manager.get(request, dataFromClientToServer));
+
+ 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.size(), 1);
+ QCOMPARE(finSpy.size(), 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);
+ QVERIFY(validateRedirectedResponseHeaders(reply));
+
+ // Verify that the message body has arrived to the server
+ if (status > 302) {
+ QVERIFY(server2.contentLength != 0);
+ QCOMPARE(server2.contentLength, dataFromClientToServer.size());
+ QCOMPARE(server2.receivedData.right(dataFromClientToServer.size()), dataFromClientToServer);
+ } else {
+ // In these cases the message body should not reach the server
+ QVERIFY(server2.contentLength == 0);
+ }
+}
+
#if QT_CONFIG(networkproxy)
void tst_QNetworkReply::headFromHttp_data()
{