diff options
| author | Mårten Nordheim <marten.nordheim@qt.io> | 2025-08-01 16:38:56 +0200 |
|---|---|---|
| committer | Mårten Nordheim <marten.nordheim@qt.io> | 2025-09-01 16:23:51 +0200 |
| commit | bb40f641f2f80ff5a01bf49d50470d6868cbf701 (patch) | |
| tree | 8406843dfa48168f6969c6eedd2b1b4b56899461 /src/network/access/qnetworkreplyhttpimpl.cpp | |
| parent | ea5471e3b84a1ccaa5b02618d91bec6130efc3b1 (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.cpp | 51 |
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; |
