summaryrefslogtreecommitdiffstats
path: root/src/network/access/qnetworkreplyhttpimpl.cpp
diff options
context:
space:
mode:
authorMårten Nordheim <marten.nordheim@qt.io>2025-08-01 16:38:56 +0200
committerMårten Nordheim <marten.nordheim@qt.io>2025-09-01 16:23:51 +0200
commitbb40f641f2f80ff5a01bf49d50470d6868cbf701 (patch)
tree8406843dfa48168f6969c6eedd2b1b4b56899461 /src/network/access/qnetworkreplyhttpimpl.cpp
parentea5471e3b84a1ccaa5b02618d91bec6130efc3b1 (diff)
HTTP: don't send content-length: 0
If a request is made with a valid QIODevice that happens to be empty then we end up setting the Content-Length header to 0. This is discouraged according to the RFC9110, specifically for methods that do not usually expect a body. Pick-to: 6.10 6.9 6.8 Fixes: QTBUG-138848 Change-Id: I618220008ed9f739fed96e5fd7630a0afd5cdaaf Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network/access/qnetworkreplyhttpimpl.cpp')
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp51
1 files changed, 51 insertions, 0 deletions
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index bed1eed63d8..61cc2f7038b 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -625,6 +625,53 @@ QHttpNetworkRequest::Priority QNetworkReplyHttpImplPrivate::convert(QNetworkRequ
Q_UNREACHABLE_RETURN(QHttpNetworkRequest::NormalPriority);
}
+void QNetworkReplyHttpImplPrivate::maybeDropUploadDevice(const QNetworkRequest &newHttpRequest)
+{
+ // Check for 0-length upload device. Following RFC9110, we are discouraged
+ // from sending "content-length: 0" for methods where a content-length would
+ // not normally be expected. E.g. get, connect, head, delete
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-5
+ auto contentLength0Allowed = [&]{
+ switch (operation) {
+ case QNetworkAccessManager::CustomOperation: {
+ const QByteArray customVerb = newHttpRequest.attribute(QNetworkRequest::CustomVerbAttribute)
+ .toByteArray();
+ if (customVerb.compare("get", Qt::CaseInsensitive) != 0
+ && customVerb.compare("head", Qt::CaseInsensitive) != 0
+ && customVerb.compare("connect", Qt::CaseInsensitive) != 0
+ && customVerb.compare("delete", Qt::CaseInsensitive) != 0) {
+ return true; // Trust user => content-length 0 is presumably okay!
+ }
+ // else:
+ [[fallthrough]];
+ }
+ case QNetworkAccessManager::HeadOperation:
+ case QNetworkAccessManager::GetOperation:
+ case QNetworkAccessManager::DeleteOperation:
+ // no content-length 0
+ return false;
+ case QNetworkAccessManager::PutOperation:
+ case QNetworkAccessManager::PostOperation:
+ case QNetworkAccessManager::UnknownOperation:
+ // yes content-length 0
+ return true;
+ }
+ Q_UNREACHABLE_RETURN(false);
+ };
+
+ const auto hasEmptyOutgoingPayload = [&]() {
+ if (!outgoingData)
+ return false;
+ if (outgoingDataBuffer)
+ return outgoingDataBuffer->isEmpty();
+ return outgoingData->size() == 0;
+ };
+ if (Q_UNLIKELY(hasEmptyOutgoingPayload()) && !contentLength0Allowed()) {
+ outgoingData = nullptr;
+ outgoingDataBuffer.reset();
+ }
+}
+
void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpRequest)
{
Q_Q(QNetworkReplyHttpImpl);
@@ -697,6 +744,10 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
httpRequest.setRedirectPolicy(redirectPolicy);
+ // If, for some reason, it turns out we won't use the upload device we drop
+ // it in the following call:
+ maybeDropUploadDevice(newHttpRequest);
+
httpRequest.setPriority(convert(newHttpRequest.priority()));
loadingFromCache = false;