From 6224df5a471e9040a99dd3dc2e91817a701b1bf6 Mon Sep 17 00:00:00 2001
From: Carlos Garcia Campos <cgarcia@igalia.com>
Date: Thu, 29 Jan 2026 16:43:28 +0100
Subject: [PATCH] server: close the connection after responsing a request
 containing Content-Length and Transfer-Encoding

Closes #475

Conflict:Function soup_message_headers_set =>Function transfer_encoding_setter and Function content_length_setter
soup_message_headers_get_one_common => soup_message_headers_get_one
soup_message_headers_replace_common => soup_message_headers_replace
SOUP_HEADER_CONTENT_LENGTH => "Content-Length"
SOUP_HEADER_TRANSFER_ENCODING => "Transfer-Encoding"
libsoup/server/http1/soup-server-message-io-http1.c => libsoup/soup-message-server-io.c
context adapt
Reference:https://gitlab.gnome.org/GNOME/libsoup/-/commit/6224df5a471e9040a99dd3dc2e91817a701b1bf6
---
 libsoup/soup-message-headers.c   | 85 +++++++++++++++-----------------
 libsoup/soup-message-server-io.c |  8 +++
 tests/server-test.c              |  4 +-
 3 files changed, 51 insertions(+), 46 deletions(-)

diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c
index cfce2f7..c3c4306 100644
--- a/libsoup/soup-message-headers.c
+++ b/libsoup/soup-message-headers.c
@@ -666,38 +666,13 @@ clear_special_headers (SoupMessageHeaders *hdrs)
 static void
 transfer_encoding_setter (SoupMessageHeaders *hdrs, const char *value)
 {
-	if (value) {
-		/* "identity" is a wrong value according to RFC errata 408,
-		 * and RFC 7230 does not list it as valid transfer-coding.
-		 * Nevertheless, the obsolete RFC 2616 stated "identity"
-		 * as valid, so we can't handle it as unrecognized here
-		 * for compatibility reasons.
-		 */
-		if (g_ascii_strcasecmp (value, "chunked") == 0)
-			hdrs->encoding = SOUP_ENCODING_CHUNKED;
-		else if (g_ascii_strcasecmp (value, "identity") != 0)
-			hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED;
-	} else
-		hdrs->encoding = -1;
+	hdrs->encoding = -1;
 }
 
 static void
 content_length_setter (SoupMessageHeaders *hdrs, const char *value)
 {
-	/* Transfer-Encoding trumps Content-Length */
-	if (hdrs->encoding == SOUP_ENCODING_CHUNKED)
-		return;
-
-	if (value) {
-		char *end;
-
-		hdrs->content_length = g_ascii_strtoull (value, &end, 10);
-		if (*end)
-			hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED;
-		else
-			hdrs->encoding = SOUP_ENCODING_CONTENT_LENGTH;
-	} else
-		hdrs->encoding = -1;
+	hdrs->encoding = -1;
 }
 
 /**
@@ -730,29 +705,49 @@ SoupEncoding
 soup_message_headers_get_encoding (SoupMessageHeaders *hdrs)
 {
-	const char *header;
+	const char *content_length;
+	const char *transfer_encoding;
 
 	if (hdrs->encoding != -1)
 		return hdrs->encoding;
 
-	/* If Transfer-Encoding was set, hdrs->encoding would already
-	 * be set. So we don't need to check that possibility.
-	 */
-	header = soup_message_headers_get_one (hdrs, "Content-Length");
-	if (header) {
-		content_length_setter (hdrs, header);
-		if (hdrs->encoding != -1)
-			return hdrs->encoding;
+	/* Transfer-Encoding is checked first because it overrides the Content-Length */
+        transfer_encoding = soup_message_headers_get_one (hdrs, "Transfer-Encoding");
+        if (transfer_encoding) {
+                /* "identity" is a wrong value according to RFC errata 408,
+                 * and RFC 7230 does not list it as valid transfer-coding.
+                 * Nevertheless, the obsolete RFC 2616 stated "identity"
+                 * as valid, so we can't handle it as unrecognized here
+                 * for compatibility reasons.
+		 */
+		if (g_ascii_strcasecmp (transfer_encoding, "chunked") == 0)
+                        hdrs->encoding = SOUP_ENCODING_CHUNKED;
+                else if (g_ascii_strcasecmp (transfer_encoding, "identity") != 0)
+                        hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED;
+        } else {
+                content_length = soup_message_headers_get_one (hdrs, "Content-Length");
+                if (content_length) {
+                        char *end;
+
+                        hdrs->content_length = g_ascii_strtoull (content_length, &end, 10);
+                        if (*end)
+                                hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED;
+                        else
+                                hdrs->encoding = SOUP_ENCODING_CONTENT_LENGTH;
+                }
+	}
+
+	if (hdrs->encoding == -1) {
+		/* Per RFC 2616 4.4, a response body that doesn't indicate its
+		 * encoding otherwise is terminated by connection close, and a
+		 * request that doesn't indicate otherwise has no body. Note
+		 * that SoupMessage calls soup_message_headers_set_encoding()
+		 * to override the response body default for our own
+		 * server-side messages.
+		 */
+		hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_RESPONSE) ?
+			SOUP_ENCODING_EOF : SOUP_ENCODING_NONE;
 	}
 
-	/* Per RFC 2616 4.4, a response body that doesn't indicate its
-	 * encoding otherwise is terminated by connection close, and a
-	 * request that doesn't indicate otherwise has no body. Note
-	 * that SoupMessage calls soup_message_headers_set_encoding()
-	 * to override the response body default for our own
-	 * server-side messages.
-	 */
-	hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_RESPONSE) ?
-		SOUP_ENCODING_EOF : SOUP_ENCODING_NONE;
 	return hdrs->encoding;
 }
 
diff --git a/libsoup/soup-message-server-io.c b/libsoup/soup-message-server-io.c
index f6e12ca..8ce70e2 100644
--- a/libsoup/soup-message-server-io.c
+++ b/libsoup/soup-message-server-io.c
@@ -81,6 +81,14 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
 			return SOUP_STATUS_BAD_REQUEST;
 	}
 
+	/* A server MAY reject a request that contains both Content-Length and
+         * Transfer-Encoding or process such a request in accordance with the
+         * Transfer-Encoding alone. Regardless, the server MUST close the connection
+         * after responding to such a request to avoid the potential attacks
+         */
+        if (*encoding == SOUP_ENCODING_CHUNKED && soup_message_headers_get_one (msg->request_headers, "Content-Length"))
+                soup_message_headers_replace (msg->request_headers, "Connection", "close");
+
 	/* Generate correct context for request */
 	req_host = soup_message_headers_get_one (msg->request_headers, "Host");
 	if (req_host && strchr (req_host, '/')) {
diff --git a/tests/server-test.c b/tests/server-test.c
index c0c777e..de52925 100644
--- a/tests/server-test.c
+++ b/tests/server-test.c
@@ -1397,7 +1397,9 @@ do_chunked_test (ServerData *sd, gconstpointer test_data)
                 const char *description;
                 const char *test;
         } tests[] = {
-                { "Lone LF", "Transfer-Encoding: chunked\r\n\r\n5;ext\n data\r\n0\r\n\r\n" },
+		{ "Single LF", "Transfer-Encoding: chunked\r\n\r\n5;ext\n data\r\n0\r\n\r\n" },
+		{ "Content-Length and Transfer-Encoding", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n" },
+		{ "Content-Length and Transfer-Encoding with keep alive connection", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\n\r\n0\r\n\r\n" },
         };
 
         sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
-- 
2.43.0