File libsoup-CVE-2026-2443.patch of Package libsoup

diff -urp libsoup-3.6.5.orig/libsoup/soup-message-headers.c libsoup-3.6.5/libsoup/soup-message-headers.c
--- libsoup-3.6.5.orig/libsoup/soup-message-headers.c	2026-02-14 04:14:09.575357979 -0600
+++ libsoup-3.6.5/libsoup/soup-message-headers.c	2026-02-14 04:17:20.278921604 -0600
@@ -1176,10 +1176,16 @@ sort_ranges (gconstpointer a, gconstpoin
 }
 
 /* like soup_message_headers_get_ranges(), except it returns:
- *   SOUP_STATUS_OK if there is no Range or it should be ignored.
- *   SOUP_STATUS_PARTIAL_CONTENT if there is at least one satisfiable range.
- *   SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE if @check_satisfiable
- *     is %TRUE and the request is not satisfiable given @total_length.
+ *  - SOUP_STATUS_OK if there is no Range or it should be ignored due to being
+ *    entirely invalid.
+ *  - SOUP_STATUS_PARTIAL_CONTENT if there is at least one satisfiable range.
+ *  - SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE if @check_satisfiable
+ *     is %TRUE, the Range is valid, but no part of the request is satisfiable
+ *     given @total_length.
+ *
+ * @ranges and @length are only set if SOUP_STATUS_PARTIAL_CONTENT is returned.
+ *
+ * See https://httpwg.org/specs/rfc9110.html#field.range
  */
 guint
 soup_message_headers_get_ranges_internal (SoupMessageHeaders  *hdrs,
@@ -1193,22 +1199,28 @@ soup_message_headers_get_ranges_internal
 	GArray *array;
 	char *spec, *end;
 	guint status = SOUP_STATUS_OK;
+	gboolean is_all_valid = TRUE;
 
 	if (!range || strncmp (range, "bytes", 5) != 0)
-		return status;
+		return SOUP_STATUS_OK;  /* invalid header or unknown range unit */
 
 	range += 5;
 	while (g_ascii_isspace (*range))
 		range++;
 	if (*range++ != '=')
-		return status;
+		return SOUP_STATUS_OK;  /* invalid header */
 	while (g_ascii_isspace (*range))
 		range++;
 
 	range_list = soup_header_parse_list (range);
 	if (!range_list)
-		return status;
+		return SOUP_STATUS_OK;  /* invalid list */
 
+	/* Loop through the ranges and modify the status accordingly. Default to
+	 * status 200 (OK, ignoring the ranges). Switch to status 206 (Partial
+	 * Content) if there is at least one partially valid range. Switch to
+	 * status 416 (Range Not Satisfiable) if there are no partially valid
+	 * ranges at all. */
 	array = g_array_new (FALSE, FALSE, sizeof (SoupRange));
 	for (r = range_list; r; r = r->next) {
 		SoupRange cur;
@@ -1221,30 +1233,44 @@ soup_message_headers_get_ranges_internal
 			cur.start = g_ascii_strtoull (spec, &end, 10);
 			if (*end == '-')
 				end++;
-			if (*end) {
+			if (*end)
 				cur.end = g_ascii_strtoull (end, &end, 10);
-				if (cur.end < cur.start) {
-					status = SOUP_STATUS_OK;
-					break;
-				}
-			} else
+			else
 				cur.end = total_length - 1;
 		}
+
 		if (*end) {
-			status = SOUP_STATUS_OK;
-			break;
-		} else if (check_satisfiable && cur.start >= total_length) {
-			if (status == SOUP_STATUS_OK)
-				status = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE;
+			/* Junk after the range */
+			is_all_valid = FALSE;
+			continue;
+		}
+
+		if (cur.end < cur.start) {
+			is_all_valid = FALSE;
 			continue;
 		}
 
+		g_assert (cur.start >= 0);
+		if (cur.end >= total_length)
+			cur.end = total_length - 1;
+
+		if (cur.start >= total_length) {
+			/* Range is valid, but unsatisfiable */
+			continue;
+		}
+
+		/* We have at least one (at least partially) satisfiable range */
 		g_array_append_val (array, cur);
 		status = SOUP_STATUS_PARTIAL_CONTENT;
 	}
 	soup_header_free_list (range_list);
 
 	if (status != SOUP_STATUS_PARTIAL_CONTENT) {
+		g_assert (status == SOUP_STATUS_OK);
+
+		if (is_all_valid && check_satisfiable)
+			status = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE;
+
 		g_array_free (array, TRUE);
 		return status;
 	}
diff -urp libsoup-3.6.5.orig/tests/range-test.c libsoup-3.6.5/tests/range-test.c
--- libsoup-3.6.5.orig/tests/range-test.c	2026-02-14 03:51:18.021654823 -0600
+++ libsoup-3.6.5/tests/range-test.c	2026-02-14 04:17:20.279517774 -0600
@@ -61,7 +61,8 @@ check_part (SoupMessageHeaders *headers,
 
 static void
 do_single_range (SoupSession *session, SoupMessage *msg,
-		 int start, int end, gboolean succeed)
+		 int start, int end, SoupStatus expected_status,
+		 int expected_start, int expected_end)
 {
 	const char *content_type;
 	GBytes *body;
@@ -71,7 +72,7 @@ do_single_range (SoupSession *session, S
 
 	body = soup_test_session_async_send (session, msg, NULL, NULL);
 
-	if (!succeed) {
+	if (expected_status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
 		soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
 		if (soup_message_get_status (msg) != SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
 			const char *content_range;
@@ -81,31 +82,81 @@ do_single_range (SoupSession *session, S
 			if (content_range)
 				debug_printf (1, "    Content-Range: %s\n", content_range);
 		}
+	} else if (expected_status == SOUP_STATUS_OK) {
+		soup_test_assert_message_status (msg, SOUP_STATUS_OK);
 
-		g_object_unref (msg);
-		return;
-	}
-
-	soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+		content_type = soup_message_headers_get_content_type (
+			soup_message_get_response_headers (msg), NULL);
+		g_assert_cmpstr (content_type, !=, "multipart/byteranges");
+
+		g_assert_false (soup_message_headers_get_content_range (
+			soup_message_get_response_headers (msg), NULL, NULL, NULL));
+		g_assert_cmpint (soup_message_headers_get_content_length (
+			soup_message_get_response_headers (msg)), ==, g_bytes_get_size (full_response));
+	} else {
+		soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+
+		content_type = soup_message_headers_get_content_type (
+			soup_message_get_response_headers (msg), NULL);
+		g_assert_cmpstr (content_type, !=, "multipart/byteranges");
 
-	content_type = soup_message_headers_get_content_type (
-		soup_message_get_response_headers (msg), NULL);
-	g_assert_cmpstr (content_type, !=, "multipart/byteranges");
+		check_part (soup_message_get_response_headers (msg), body, TRUE, expected_start, expected_end);
+	}
 
-	check_part (soup_message_get_response_headers (msg), body, TRUE, start, end);
-	g_bytes_unref (body);
+	g_clear_pointer (&body, g_bytes_unref);
 	g_object_unref (msg);
 }
 
 static void
 request_single_range (SoupSession *session, const char *uri,
-		      int start, int end, gboolean succeed)
+		      int start, int end, SoupStatus expected_status,
+		      int expected_start, int expected_end)
 {
 	SoupMessage *msg;
 
 	msg = soup_message_new ("GET", uri);
 	soup_message_headers_set_range (soup_message_get_request_headers (msg), start, end);
-	do_single_range (session, msg, start, end, succeed);
+	do_single_range (session, msg, start, end, expected_status, expected_start, expected_end);
+}
+
+/* This always asserts failure (either 406 or 200 with no Content-Range); it’s
+ * intended to be used for passing invalid
+ * Range header formats which can’t be built by calling
+ * soup_message_headers_set_range(). */
+static void
+request_single_range_by_string (SoupSession *session, const char *uri,
+			        const char *range, SoupStatus expected_status)
+{
+	SoupMessage *msg;
+	GBytes *body;
+
+	msg = soup_message_new ("GET", uri);
+	soup_message_headers_replace (soup_message_get_request_headers (msg), "Range", range);
+
+	debug_printf (1, "    Range: %s\n",
+		      soup_message_headers_get_one (soup_message_get_request_headers (msg), "Range"));
+
+	body = soup_test_session_async_send (session, msg, NULL, NULL);
+
+	if (expected_status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
+		soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
+	} else {
+		const char *content_type;
+
+		soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+
+		content_type = soup_message_headers_get_content_type (
+			soup_message_get_response_headers (msg), NULL);
+		g_assert_cmpstr (content_type, !=, "multipart/byteranges");
+
+		g_assert_false (soup_message_headers_get_content_range (
+			soup_message_get_response_headers (msg), NULL, NULL, NULL));
+		g_assert_cmpint (soup_message_headers_get_content_length (
+			soup_message_get_response_headers (msg)), ==, g_bytes_get_size (full_response));
+	}
+
+	g_clear_pointer (&body, g_bytes_unref);
+	g_object_unref (msg);
 }
 
 static void
@@ -172,7 +223,9 @@ request_double_range (SoupSession *sessi
 		do_single_range (session, msg,
 				 MIN (first_start, second_start),
 				 MAX (first_end, second_end),
-				 TRUE);
+				 SOUP_STATUS_PARTIAL_CONTENT,
+				 MIN (first_start, second_start),
+				 MAX (first_end, second_end));
 	} else
 		do_multi_range (session, msg, expected_return_ranges);
 }
@@ -200,7 +253,9 @@ request_triple_range (SoupSession *sessi
 		do_single_range (session, msg,
 				 MIN (first_start, MIN (second_start, third_start)),
 				 MAX (first_end, MAX (second_end, third_end)),
-				 TRUE);
+				 SOUP_STATUS_PARTIAL_CONTENT,
+				 MIN (first_start, MIN (second_start, third_start)),
+				 MAX (first_end, MAX (second_end, third_end)));
 	} else
 		do_multi_range (session, msg, expected_return_ranges);
 }
@@ -256,7 +311,8 @@ do_range_test (SoupSession *session, con
 	debug_printf (1, "Requesting %d-%d\n", 0 * twelfths, 1 * twelfths);
 	request_single_range (session, uri,
 			      0 * twelfths, 1 * twelfths,
-			      TRUE);
+			      SOUP_STATUS_PARTIAL_CONTENT,
+			      0 * twelfths, 1 * twelfths);
 
 	/* B: 11, end-relative request. These two are mostly redundant
 	 * in terms of data coverage, but they may still catch
@@ -265,11 +321,13 @@ do_range_test (SoupSession *session, con
 	debug_printf (1, "Requesting %d-\n", 11 * twelfths);
 	request_single_range (session, uri,
 			      11 * twelfths, -1,
-			      TRUE);
+			      SOUP_STATUS_PARTIAL_CONTENT,
+			      11 * twelfths, -1);
 	debug_printf (1, "Requesting -%d\n", 1 * twelfths);
 	request_single_range (session, uri,
 			      -1 * twelfths, -1,
-			      TRUE);
+			      SOUP_STATUS_PARTIAL_CONTENT,
+			      -1 * twelfths, -1);
 
 	/* C: 2 and 5 */
 	debug_printf (1, "Requesting %d-%d,%d-%d\n",
@@ -322,7 +380,8 @@ do_range_test (SoupSession *session, con
 		      (int) full_response_length + 100);
 	request_single_range (session, uri,
 			      full_response_length + 1, full_response_length + 100,
-			      FALSE);
+			      SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE,
+			      0, 0);
 
 	debug_printf (1, "Requesting (semi-invalid) 1-10,%d-%d,20-30\n",
 		      (int) full_response_length + 1,
@@ -331,6 +390,57 @@ do_range_test (SoupSession *session, con
 				    1, 10,
 				    full_response_length + 1, full_response_length + 100,
 				    20, 30); 
+
+	debug_printf (1, "Requesting (invalid end) %d-%d\n",
+		      1,
+		      (int) full_response_length + 1000);
+	request_single_range (session, uri,
+			      1, full_response_length + 1000,
+			      SOUP_STATUS_PARTIAL_CONTENT,
+			      1, full_response_length - 1);
+
+	debug_printf (1, "Requesting (end before start) %d-%d\n",
+		      10,
+		      1);
+	request_single_range (session, uri,
+			      10, 1,
+			      SOUP_STATUS_OK,
+			      1, full_response_length);
+
+	debug_printf (1, "Requesting (malformed suffix length) -0\n");
+	request_single_range_by_string (session, uri,
+					"bytes=-0",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (extra content after valid header value) 0-10\n");
+	request_single_range_by_string (session, uri,
+					"bytes=0-10 but with weird trailing content",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (invalid range dash) 0a10\n");
+	request_single_range_by_string (session, uri,
+					"bytes=0a10",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (invalid range unit) 0-10\n");
+	request_single_range_by_string (session, uri,
+					"horses=0-10",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (missing equals) 0-10\n");
+	request_single_range_by_string (session, uri,
+					"bytes 0-10",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (end before start but with whitespace) 10-1\n");
+	request_single_range_by_string (session, uri,
+					"bytes \t = \t 10-1",
+					SOUP_STATUS_OK);
+
+	debug_printf (1, "Requesting (delimiters but no ranges)\n");
+	request_single_range_by_string (session, uri,
+					"bytes=, ,,\t, ",
+					SOUP_STATUS_OK);
 }
 
 static void
openSUSE Build Service is sponsored by