OBS-URL: https://build.opensuse.org/package/show/GNOME:Factory/libsoup?expand=0&rev=329
212 lines
9.0 KiB
Diff
212 lines
9.0 KiB
Diff
From e032d3e9b0a27d10597398023532dd8f9b6654cf Mon Sep 17 00:00:00 2001
|
|
From: Carlos Garcia Campos <cgarcia@igalia.com>
|
|
Date: Tue, 17 Feb 2026 16:39:26 +0100
|
|
Subject: [PATCH] Do not allow adding multiple content length values to headers
|
|
|
|
Closes #500
|
|
---
|
|
libsoup/soup-message-headers.c | 27 ++++++++++++++
|
|
tests/header-parsing-test.c | 50 +++++++++++++++++++++++++-
|
|
tests/server-test.c | 64 ++++++++++++++++++++++++++++++++++
|
|
3 files changed, 140 insertions(+), 1 deletion(-)
|
|
|
|
diff -urp libsoup-3.6.6.orig/libsoup/soup-message-headers.c libsoup-3.6.6/libsoup/soup-message-headers.c
|
|
--- libsoup-3.6.6.orig/libsoup/soup-message-headers.c 2026-02-20 10:17:04.610154739 -0600
|
|
+++ libsoup-3.6.6/libsoup/soup-message-headers.c 2026-02-20 10:18:22.593721498 -0600
|
|
@@ -269,6 +269,33 @@ soup_message_headers_append_common (Soup
|
|
return FALSE;
|
|
}
|
|
|
|
+ if (name == SOUP_HEADER_CONTENT_LENGTH) {
|
|
+ /* RFC 9110 - 7.7. Content-Length
|
|
+ * If a message is received that has a Content-Length header field value consisting of
|
|
+ * the same decimal value as a comma-separated list (Section 5.7.1) — for example,
|
|
+ * "Content-Length: 42, 42" — indicating that duplicate Content-Length header fields have
|
|
+ * been generated or combined by an upstream message processor, then the recipient must either
|
|
+ * reject the message as invalid or replace the duplicated field values with a single valid
|
|
+ * Content-Length field containing that decimal value prior to determining the message body
|
|
+ * length or forwarding the message.
|
|
+ */
|
|
+ const char *content_length = soup_message_headers_get_one_common (hdrs, SOUP_HEADER_CONTENT_LENGTH);
|
|
+ if (content_length) {
|
|
+ guint64 decimal_value1, decimal_value2;
|
|
+ char *end;
|
|
+
|
|
+ decimal_value1 = g_ascii_strtoull (content_length, &end, 10);
|
|
+ if (*end)
|
|
+ return FALSE;
|
|
+
|
|
+ decimal_value2 = g_ascii_strtoull (value, &end, 10);
|
|
+ if (*end)
|
|
+ return FALSE;
|
|
+
|
|
+ return decimal_value1 == decimal_value2;
|
|
+ }
|
|
+ }
|
|
+
|
|
if (trusted_value == SOUP_HEADER_VALUE_UNTRUSTED && !is_valid_header_value (value)) {
|
|
g_warning ("soup_message_headers_append: Rejecting bad value '%s'", value);
|
|
return FALSE;
|
|
diff -urp libsoup-3.6.6.orig/tests/header-parsing-test.c libsoup-3.6.6/tests/header-parsing-test.c
|
|
--- libsoup-3.6.6.orig/tests/header-parsing-test.c 2026-02-20 10:17:04.623155000 -0600
|
|
+++ libsoup-3.6.6/tests/header-parsing-test.c 2026-02-20 10:18:22.594721518 -0600
|
|
@@ -368,6 +368,22 @@ static struct RequestTest {
|
|
}, 0
|
|
},
|
|
|
|
+ { "Duplicate Content-Length with the same value", NULL,
|
|
+ "POST / HTTP/1.1\r\nContent-Length: 4\r\nContent-Length: 4\r\n",
|
|
+ -1,
|
|
+ SOUP_STATUS_OK,
|
|
+ "POST", "/", SOUP_HTTP_1_1,
|
|
+ { { "Content-Length", "4" } }, 0
|
|
+ },
|
|
+
|
|
+ { "Duplicate Content-Length with the same decimal value", NULL,
|
|
+ "POST / HTTP/1.1\r\nContent-Length: 04\r\nContent-Length: 4\r\n",
|
|
+ -1,
|
|
+ SOUP_STATUS_OK,
|
|
+ "POST", "/", SOUP_HTTP_1_1,
|
|
+ { { "Content-Length", "04" } }, 0
|
|
+ },
|
|
+
|
|
/************************/
|
|
/*** INVALID REQUESTS ***/
|
|
/************************/
|
|
@@ -507,7 +523,16 @@ static struct RequestTest {
|
|
NULL, NULL, -1,
|
|
{ { NULL } },
|
|
G_LOG_LEVEL_WARNING
|
|
- }
|
|
+ },
|
|
+
|
|
+ { "Duplicate Content-Length with different value",
|
|
+ "https://gitlab.gnome.org/GNOME/libsoup/-/issues/500",
|
|
+ "POST / HTTP/1.1\r\nContent-Length: 2\r\nContent-Length: 4\r\n",
|
|
+ -1,
|
|
+ SOUP_STATUS_BAD_REQUEST,
|
|
+ NULL, NULL, -1,
|
|
+ { { NULL } }, 0
|
|
+ }
|
|
};
|
|
static const int num_reqtests = G_N_ELEMENTS (reqtests);
|
|
|
|
@@ -1475,6 +1500,28 @@ do_append_duplicate_host_test (void)
|
|
soup_message_headers_unref (hdrs);
|
|
}
|
|
|
|
+static void
|
|
+do_append_duplicate_content_length_test (void)
|
|
+{
|
|
+ SoupMessageHeaders *hdrs;
|
|
+ const char *list_value;
|
|
+
|
|
+ hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
|
|
+ soup_message_headers_append (hdrs, "Content-Length", "42");
|
|
+
|
|
+ /* Inserting the same value doesn't generate a list */
|
|
+ soup_message_headers_append (hdrs, "Content-Length", "42");
|
|
+ list_value = soup_message_headers_get_list (hdrs, "Content-Length");
|
|
+ g_assert_cmpstr (list_value, ==, "42");
|
|
+
|
|
+ /* Inserting a different value does nothing */
|
|
+ soup_message_headers_append (hdrs, "Content-Length", "45");
|
|
+ list_value = soup_message_headers_get_list (hdrs, "Content-Length");
|
|
+ g_assert_cmpstr (list_value, ==, "42");
|
|
+
|
|
+ soup_message_headers_unref (hdrs);
|
|
+}
|
|
+
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
@@ -1491,6 +1538,7 @@ main (int argc, char **argv)
|
|
g_test_add_func ("/header-parsing/append-param", do_append_param_tests);
|
|
g_test_add_func ("/header-parsing/bad", do_bad_header_tests);
|
|
g_test_add_func ("/header-parsing/append-duplicate-host", do_append_duplicate_host_test);
|
|
+ g_test_add_func ("/header-parsing/append-duplicate-content-length", do_append_duplicate_content_length_test);
|
|
|
|
ret = g_test_run ();
|
|
|
|
diff -urp libsoup-3.6.6.orig/tests/server-test.c libsoup-3.6.6/tests/server-test.c
|
|
--- libsoup-3.6.6.orig/tests/server-test.c 2026-02-20 10:17:04.636155262 -0600
|
|
+++ libsoup-3.6.6/tests/server-test.c 2026-02-20 10:18:22.595696993 -0600
|
|
@@ -1422,6 +1422,68 @@ do_chunked_test (ServerData *sd, gconstp
|
|
}
|
|
}
|
|
|
|
+static void
|
|
+do_multiple_content_length_test (ServerData *sd, gconstpointer test_data)
|
|
+{
|
|
+ gint i;
|
|
+ struct {
|
|
+ const char *description;
|
|
+ const char *test;
|
|
+ const char *expected_response;
|
|
+ } tests[] = {
|
|
+ { "Double Content-Length with different value", "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 0\r\nContent-Length: 4\r\nConnection: close\r\n\r\n\r\nABCD", "HTTP/1.0 400 Bad Request" },
|
|
+ { "Double Content-Length with the same value", "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 4\r\nContent-Length: 4\r\nConnection: close\r\n\r\n\r\nABCD", "HTTP/1.1 200 OK" },
|
|
+ };
|
|
+
|
|
+ sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
|
|
+ sd->base_uri = soup_test_server_get_uri (sd->server, "http", NULL);
|
|
+ server_add_handler (sd, NULL, server_callback, NULL, NULL);
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
|
|
+ GSocketClient *client;
|
|
+ GSocketConnection *conn;
|
|
+ GInputStream *input;
|
|
+ GOutputStream *output;
|
|
+ gsize nwritten;
|
|
+ char buffer[4096];
|
|
+ gssize nread;
|
|
+ GString *response;
|
|
+ const char *boundary;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ debug_printf (1, " %s\n", tests[i].description);
|
|
+
|
|
+ client = g_socket_client_new ();
|
|
+ conn = g_socket_client_connect_to_host (client, g_uri_get_host (sd->base_uri), g_uri_get_port (sd->base_uri), NULL, &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ output = g_io_stream_get_output_stream (G_IO_STREAM (conn));
|
|
+ g_output_stream_write_all (output, tests[i].test, strlen (tests[i].test), &nwritten, NULL, &error);
|
|
+ g_assert_no_error (error);
|
|
+ g_assert_cmpuint (nwritten, ==, strlen (tests[i].test));
|
|
+ g_output_stream_flush (output, NULL, &error);
|
|
+ g_assert_no_error (error);
|
|
+
|
|
+ response = g_string_new (NULL);
|
|
+
|
|
+ input = g_io_stream_get_input_stream (G_IO_STREAM (conn));
|
|
+ do {
|
|
+ nread = g_input_stream_read (input, buffer, sizeof(buffer), NULL, NULL);
|
|
+ if (nread >= 0)
|
|
+ response = g_string_append_len (response, (const char *)buffer, nread);
|
|
+ } while (nread > 0);
|
|
+
|
|
+ boundary = strstr (response->str, "\r\n");
|
|
+ g_assert_nonnull (boundary);
|
|
+ response = g_string_truncate (response, response->len - strlen (boundary));
|
|
+ g_assert_cmpstr (response->str, ==, tests[i].expected_response);
|
|
+ g_string_free (response, TRUE);
|
|
+
|
|
+ g_object_unref (conn);
|
|
+ g_object_unref (client);
|
|
+ }
|
|
+}
|
|
+
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
@@ -1464,6 +1526,8 @@ main (int argc, char **argv)
|
|
server_setup, do_steal_connect_test, server_teardown);
|
|
g_test_add ("/server/chunked", ServerData, NULL,
|
|
NULL, do_chunked_test, server_teardown);
|
|
+ g_test_add ("/server/multiple-content-length", ServerData, NULL,
|
|
+ NULL, do_multiple_content_length_test, server_teardown);
|
|
|
|
ret = g_test_run ();
|
|
|