diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 1c604a01ecb..4ca51fb98f9 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -779,6 +779,46 @@ QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request) return d_func()->postProcess(createRequest(QNetworkAccessManager::GetOperation, 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 diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index f0c99c03b86..5704061f153 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 30fa1a1e524..70533a229e0 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 ec8b341efa3..c169e77cac7 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("dataFromClientToServer"); + QTest::addColumn("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("dataFromClientToServer"); + QTest::addColumn("alwaysCache"); + QTest::addColumn("requestReturn"); + QTest::addColumn("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("dataFromClientToServer"); + QTest::addColumn("useDevice"); + QTest::addColumn("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 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() {