debdiff libsoup3_3.6.5-{1,2}.dsc | filterdiff -p1 -x'debian/patches/*.patch' -x'debian/patches/debian/*.patch'

diff -Nru libsoup3-3.6.5/debian/changelog libsoup3-3.6.5/debian/changelog
--- libsoup3-3.6.5/debian/changelog	2025-03-21 21:04:16.000000000 +0000
+++ libsoup3-3.6.5/debian/changelog	2025-07-12 09:52:52.000000000 +0100
@@ -1,3 +1,76 @@
+libsoup3 (3.6.5-2) unstable; urgency=medium
+
+  * Team upload
+  * d/patches: Re-export patch series (no functional changes)
+  * d/p/multipart-Fix-read-out-of-buffer-bounds-under-soup_multip.patch:
+    Add patch from upstream git to fix multipart message parsing.
+    Previously this could read outside the buffer.
+    This change isn't on upstream's 3.6.x branch yet, so take it from
+    3.7.x. Test coverage is included.
+    (CVE-2025-32914, Closes: #1103267)
+  * d/p/soup-server-http2-Check-validity-of-the-constructed-conne.patch,
+    d/p/soup-server-http2-Correct-check-of-the-validity-of-the-co.patch:
+    Add patch from upstream git to fix denial of service in HTTP/2 server.
+    The original change does not seem to have been fully correct; a
+    follow-up fix for it is also included.
+    (CVE-2025-32908, Closes: #1103265)
+  * d/p/auth-digest-fix-crash-in-soup_auth_digest_get_protection_.patch:
+    Add patch from upstream git to fix denial of service (a crash)
+    if a libsoup client is connected to a malicious server.
+    (CVE-2025-4476, Closes: #1105887)
+  * d/p/soup-message-headers-Correct-merge-of-ranges.patch,
+    d/p/server-mem-limit-test-Limit-memory-usage-only-when-not-bu.patch:
+    Add patch from upstream git fixing server-side DoS in Range requests,
+    with a follow-up patch to make the newly added test work when compiled
+    with AddressSanitizer.
+    (CVE-2025-32907, Closes: #1103264)
+  * d/p/soup-multipart-Verify-boundary-limits-for-multipart-body.patch:
+    Add patch from upstream git fixing denial of service with crafted
+    multipart body.
+    (CVE-2025-4948, Closes: #1106204)
+  * d/p/soup-multipart-Verify-array-bounds-before-accessing-its-m.patch:
+    Add patch from upstream git fixing another denial of service with
+    crafted multipart body.
+    (CVE-2025-4969, Closes: #1106248)
+  * d/p/soup-date-utils-Add-value-checks-for-date-time-parsing.patch,
+    d/p/tests-Add-tests-for-date-time-including-timezone-validati.patch:
+    Add patch from upstream git fixing date/time validation, and expand
+    test coverage for this area.
+    (CVE-2025-4945, Closes: #1106205)
+  * d/p/soup-form-Fix-a-possible-memory-leak-in-soup_form_decode_.patch:
+    Add patch from upstream git fixing some memory leaks
+  * d/p/websocket-test-Fix-two-memory-leaks.patch,
+    d/p/misc-test-Fix-two-memory-leaks.patch,
+    d/p/http2-test-Fix-several-memory-leaks.patch,
+    d/p/range-test-Fix-a-memory-leak.patch:
+    Add patches from upstream git fixing some memory leaks in tests.
+    These are certainly not denial-of-service issues, but it makes "real"
+    memory leaks harder to detect if there are benign memory leaks in
+    the test code.
+  * d/p/test-utils-flush-stdout-after-printing.patch:
+    Add patch from upstream git to improve test logging.
+    This does not change production code, and should make it somewhat
+    less difficult to diagnose the root cause of test failures.
+    (Maybe helps: #1035983, #1109107, #1109108, #1109120)
+  * d/p/test-utils-fix-deadlock-in-add_listener_in_thread.patch:
+    Add patch from upstream git to fix a deadlock during testing.
+    This hopefully addresses one of the many sources of low-probability test
+    failures that add up to a noticeable probability of the test suite
+    as a whole failing (see also #1035983). (Closes: #1109120)
+  * d/p/tests-Treat-multithread-test-as-an-Apache-test.patch:
+    Add patch to treat multithread-test like other Apache-based tests,
+    so that it will not be run in parallel with others.
+    (Maybe helps: #1035983)
+  * d/rules: Capture test output into the buildd log, even if successful.
+    If we don't have the output from successful test logs, it's more
+    difficult to assess whether workarounds have helped, because we won't
+    see whether the situation needing the workaround was ever triggered.
+  * d/p/debian/docs-Remove-remotely-accessed-logo.patch:
+    Remove remote logo references from local documentation, improving privacy
+    and fixing a Lintian warning
+
+ -- Simon McVittie <smcv@debian.org>  Sat, 12 Jul 2025 09:52:52 +0100
+
 libsoup3 (3.6.5-1) unstable; urgency=high
 
   * New upstream release
diff -Nru libsoup3-3.6.5/debian/patches/series libsoup3-3.6.5/debian/patches/series
--- libsoup3-3.6.5/debian/patches/series	2025-03-21 21:04:16.000000000 +0000
+++ libsoup3-3.6.5/debian/patches/series	2025-07-12 09:52:52.000000000 +0100
@@ -2,3 +2,22 @@
 Record-Apache-error-log-for-unit-tests-and-show-it-during.patch
 test-utils-Add-more-debug-for-starting-stopping-Apache.patch
 tests-extend-timeout-for-http2-body-stream-test.patch
+multipart-Fix-read-out-of-buffer-bounds-under-soup_multip.patch
+soup-server-http2-Check-validity-of-the-constructed-conne.patch
+soup-server-http2-Correct-check-of-the-validity-of-the-co.patch
+auth-digest-fix-crash-in-soup_auth_digest_get_protection_.patch
+test-utils-flush-stdout-after-printing.patch
+test-utils-fix-deadlock-in-add_listener_in_thread.patch
+tests-Treat-multithread-test-as-an-Apache-test.patch
+soup-form-Fix-a-possible-memory-leak-in-soup_form_decode_.patch
+soup-message-headers-Correct-merge-of-ranges.patch
+server-mem-limit-test-Limit-memory-usage-only-when-not-bu.patch
+websocket-test-Fix-two-memory-leaks.patch
+misc-test-Fix-two-memory-leaks.patch
+http2-test-Fix-several-memory-leaks.patch
+range-test-Fix-a-memory-leak.patch
+soup-multipart-Verify-boundary-limits-for-multipart-body.patch
+soup-multipart-Verify-array-bounds-before-accessing-its-m.patch
+soup-date-utils-Add-value-checks-for-date-time-parsing.patch
+tests-Add-tests-for-date-time-including-timezone-validati.patch
+debian/docs-Remove-remotely-accessed-logo.patch
diff -Nru libsoup3-3.6.5/debian/rules libsoup3-3.6.5/debian/rules
--- libsoup3-3.6.5/debian/rules	2025-03-21 21:04:16.000000000 +0000
+++ libsoup3-3.6.5/debian/rules	2025-07-12 09:52:52.000000000 +0100
@@ -33,7 +33,7 @@
 		$(SYSPROF)
 
 override_dh_auto_test-arch:
-	dh_auto_test -- --timeout-multiplier 6
+	dh_auto_test -- --timeout-multiplier 6 --verbose
 
 # debhelper >= 13.4 makes all of /usr/libexec executable, which is not
 # quite right for installed-tests
diff -Nru libsoup3-3.6.5/docs/reference/libsoup.toml.in libsoup3-3.6.5/docs/reference/libsoup.toml.in
--- libsoup3-3.6.5/docs/reference/libsoup.toml.in	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/docs/reference/libsoup.toml.in	2025-07-12 12:28:16.000000000 +0100
@@ -5,7 +5,6 @@
 repository_url = "https://gitlab.gnome.org/GNOME/libsoup.git"
 docs_url = "https://libsoup.gnome.org/libsoup-3.0/"
 website_url = "https://libsoup.gnome.org/"
-logo_url = "https://gitlab.gnome.org/uploads/-/system/project/avatar/1630/logo.png"
 authors = "Dan Winship, Claudio Saavedra, Patrick Griffis, and Carlos Garcia Campos"
 license = "LGPL-2.0-or-later"
 description = "HTTP client/server library for GNOME"
diff -Nru libsoup3-3.6.5/libsoup/auth/soup-auth-digest.c libsoup3-3.6.5/libsoup/auth/soup-auth-digest.c
--- libsoup3-3.6.5/libsoup/auth/soup-auth-digest.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/auth/soup-auth-digest.c	2025-07-12 12:28:16.000000000 +0100
@@ -220,7 +220,7 @@
 			if (uri &&
                             g_strcmp0 (g_uri_get_scheme (uri), g_uri_get_scheme (source_uri)) == 0 &&
 			    g_uri_get_port (uri) == g_uri_get_port (source_uri) &&
-			    !strcmp (g_uri_get_host (uri), g_uri_get_host (source_uri)))
+			    !g_strcmp0 (g_uri_get_host (uri), g_uri_get_host (source_uri)))
 				dir = g_strdup (g_uri_get_path (uri));
 			else
 				dir = NULL;
diff -Nru libsoup3-3.6.5/libsoup/server/http2/soup-server-message-io-http2.c libsoup3-3.6.5/libsoup/server/http2/soup-server-message-io-http2.c
--- libsoup3-3.6.5/libsoup/server/http2/soup-server-message-io-http2.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/server/http2/soup-server-message-io-http2.c	2025-07-12 12:28:16.000000000 +0100
@@ -771,9 +771,18 @@
                 char *uri_string;
                 GUri *uri;
 
-                uri_string = g_strdup_printf ("%s://%s%s", msg_io->scheme, msg_io->authority, msg_io->path);
+                if (msg_io->authority == NULL) {
+                        io->in_callback--;
+                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+                }
+                /* RFC 5740: the CONNECT has unset the "scheme" and "path", but the GUri requires the scheme, thus let it be "(null)" */
+                uri_string = g_strdup_printf ("%s://%s%s", msg_io->scheme, msg_io->authority, msg_io->path == NULL ? "" : msg_io->path);
                 uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL);
                 g_free (uri_string);
+                if (uri == NULL) {
+                        io->in_callback--;
+                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+                }
                 soup_server_message_set_uri (msg_io->msg, uri);
                 g_uri_unref (uri);
 
diff -Nru libsoup3-3.6.5/libsoup/soup-date-utils.c libsoup3-3.6.5/libsoup/soup-date-utils.c
--- libsoup3-3.6.5/libsoup/soup-date-utils.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/soup-date-utils.c	2025-07-12 12:28:16.000000000 +0100
@@ -40,7 +40,7 @@
         g_return_val_if_fail (date != NULL, TRUE);
 
 	/* optimization */
-	if (g_date_time_get_year (date) < 2020)
+	if (g_date_time_get_year (date) < 2025)
 		return TRUE;
 
 	return g_date_time_to_unix (date) < time (NULL);
@@ -129,7 +129,7 @@
 	while (*end == ' ' || *end == '-')
 		end++;
 	*date_string = end;
-	return TRUE;
+	return *day >= 1 && *day <= 31;
 }
 
 static inline gboolean
@@ -169,7 +169,7 @@
 	while (*end == ' ' || *end == '-')
 		end++;
 	*date_string = end;
-	return TRUE;
+	return *year > 0 && *year < 9999;
 }
 
 static inline gboolean
@@ -193,7 +193,7 @@
 	while (*p == ' ')
 		p++;
 	*date_string = p;
-	return TRUE;
+	return *hour >= 0 && *hour < 24 && *minute >= 0 && *minute < 60 && *second >= 0 && *second < 60;
 }
 
 static inline gboolean
@@ -209,20 +209,25 @@
 		gulong val;
 		int sign = (**date_string == '+') ? 1 : -1;
 		val = strtoul (*date_string + 1, (char **)date_string, 10);
-		if (**date_string == ':')
-			val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
-		else
+		if (val > 9999)
+			return FALSE;
+		if (**date_string == ':') {
+			gulong val2 = strtoul (*date_string + 1, (char **)date_string, 10);
+			if (val > 99 || val2 > 99)
+				return FALSE;
+			val = 60 * val + val2;
+		} else
 			val =  60 * (val / 100) + (val % 100);
 		offset_minutes = sign * val;
-                utc = (sign == -1) && !val;
+		utc = (sign == -1) && !val;
 	} else if (**date_string == 'Z') {
 		offset_minutes = 0;
-                utc = TRUE;
+		utc = TRUE;
 		(*date_string)++;
 	} else if (!strcmp (*date_string, "GMT") ||
 		   !strcmp (*date_string, "UTC")) {
 		offset_minutes = 0;
-                utc = TRUE;
+		utc = TRUE;
 		(*date_string) += 3;
 	} else if (strchr ("ECMP", **date_string) &&
 		   ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
@@ -264,7 +269,8 @@
 		if (!parse_month (&month, &date_string) ||
 		    !parse_day (&day, &date_string) ||
 		    !parse_time (&hour, &minute, &second, &date_string) ||
-		    !parse_year (&year, &date_string))
+		    !parse_year (&year, &date_string) ||
+		    !g_date_valid_dmy (day, month, year))
 			return NULL;
 
 		/* There shouldn't be a timezone, but check anyway */
@@ -276,7 +282,8 @@
 		if (!parse_day (&day, &date_string) ||
 		    !parse_month (&month, &date_string) ||
 		    !parse_year (&year, &date_string) ||
-		    !parse_time (&hour, &minute, &second, &date_string))
+		    !parse_time (&hour, &minute, &second, &date_string) ||
+		    !g_date_valid_dmy (day, month, year))
 			return NULL;
 
 		/* This time there *should* be a timezone, but we
diff -Nru libsoup3-3.6.5/libsoup/soup-form.c libsoup3-3.6.5/libsoup/soup-form.c
--- libsoup3-3.6.5/libsoup/soup-form.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/soup-form.c	2025-07-12 12:28:16.000000000 +0100
@@ -168,12 +168,18 @@
 		}
 
 		if (file_control_name && !strcmp (name, file_control_name)) {
-			if (filename)
+			if (filename) {
+				g_free (*filename);
 				*filename = g_strdup (g_hash_table_lookup (params, "filename"));
-			if (content_type)
+			}
+			if (content_type) {
+				g_free (*content_type);
 				*content_type = g_strdup (soup_message_headers_get_content_type (part_headers, NULL));
-			if (file)
+			}
+			if (file) {
+				g_clear_pointer (file, g_bytes_unref);
 				*file = g_bytes_ref (part_body);
+			}
 		} else {
 			g_hash_table_insert (form_data_set,
 					     g_strdup (name),
diff -Nru libsoup3-3.6.5/libsoup/soup-message-headers.c libsoup3-3.6.5/libsoup/soup-message-headers.c
--- libsoup3-3.6.5/libsoup/soup-message-headers.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/soup-message-headers.c	2025-07-12 12:28:16.000000000 +0100
@@ -1244,6 +1244,7 @@
 			if (cur->start <= prev->end) {
 				prev->end = MAX (prev->end, cur->end);
 				g_array_remove_index (array, i);
+				i--;
 			}
 		}
 	}
diff -Nru libsoup3-3.6.5/libsoup/soup-multipart.c libsoup3-3.6.5/libsoup/soup-multipart.c
--- libsoup3-3.6.5/libsoup/soup-multipart.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/libsoup/soup-multipart.c	2025-07-12 12:28:16.000000000 +0100
@@ -104,7 +104,7 @@
 			continue;
 
 		/* Check that it's at start of line */
-		if (!(b == start || (b[-1] == '\n' && b[-2] == '\r')))
+		if (!(b == start || (b - start >= 2 && b[-1] == '\n' && b[-2] == '\r')))
 			continue;
 
 		/* Check for "--" or "\r\n" after boundary */
@@ -173,7 +173,7 @@
 			return NULL;
 		}
 
-		split = strstr (start, "\r\n\r\n");
+		split = g_strstr_len (start, body_end - start, "\r\n\r\n");
 		if (!split || split > end) {
 			soup_multipart_free (multipart);
 			return NULL;
@@ -204,7 +204,7 @@
 		 */
 		part_body = g_bytes_new_from_bytes (body, // FIXME
 						    split - body_data,
-						    end - 2 - split);
+						    end - 2 >= split ? end - 2 - split : 0);
 		g_ptr_array_add (multipart->bodies, part_body);
 
 		start = end;
diff -Nru libsoup3-3.6.5/meson.build libsoup3-3.6.5/meson.build
--- libsoup3-3.6.5/meson.build	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/meson.build	2025-07-12 12:28:16.000000000 +0100
@@ -357,6 +357,10 @@
 
 prefix = get_option('prefix')
 
+if get_option('b_sanitize') != 'none'
+  cdata.set_quoted('B_SANITIZE_OPTION', get_option('b_sanitize'))
+endif
+
 cdata.set_quoted('PACKAGE_VERSION', soup_version)
 cdata.set_quoted('LOCALEDIR', join_paths(prefix, get_option('localedir')))
 cdata.set_quoted('GETTEXT_PACKAGE', libsoup_api_name)
diff -Nru libsoup3-3.6.5/tests/cookies-test.c libsoup3-3.6.5/tests/cookies-test.c
--- libsoup3-3.6.5/tests/cookies-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/cookies-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -461,6 +461,16 @@
 }
 
 static void
+do_cookies_parsing_int32_overflow (void)
+{
+	SoupCookie *cookie = soup_cookie_parse ("Age=1;expires=3Mar9    999:9:9+ 999999999-age=main=gne=", NULL);
+	g_test_bug ("https://gitlab.gnome.org/GNOME/libsoup/-/issues/448");
+	g_assert_nonnull (cookie);
+	g_assert_null (soup_cookie_get_expires (cookie));
+	soup_cookie_free (cookie);
+}
+
+static void
 do_cookies_equal_nullpath (void)
 {
 	SoupCookie *cookie1, *cookie2;
@@ -718,6 +728,7 @@
 	g_test_add_func ("/cookies/parsing/no-path-null-origin", do_cookies_parsing_nopath_nullorigin);
 	g_test_add_func ("/cookies/parsing/max-age-int32-overflow", do_cookies_parsing_max_age_int32_overflow);
 	g_test_add_func ("/cookies/parsing/max-age-long-overflow", do_cookies_parsing_max_age_long_overflow);
+	g_test_add_func ("/cookies/parsing/int32-overflow", do_cookies_parsing_int32_overflow);
 	g_test_add_func ("/cookies/parsing/equal-nullpath", do_cookies_equal_nullpath);
 	g_test_add_func ("/cookies/parsing/control-characters", do_cookies_parsing_control_characters);
         g_test_add_func ("/cookies/parsing/name-value-max-size", do_cookies_parsing_name_value_max_size);
diff -Nru libsoup3-3.6.5/tests/date-test.c libsoup3-3.6.5/tests/date-test.c
--- libsoup3-3.6.5/tests/date-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/date-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -92,6 +92,11 @@
 	{ "Sat, 06 Nov 2004 08:09:07", NULL },
 	{ "06 Nov 2004 08:09:07 GMT", NULL },
 	{ "SAT, 06 NOV 2004 08:09:07 +1000", "644048" },
+	{ "Sat, 06-Nov-2004 08:09:07 -10000", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+	{ "Sat, 06-Nov-2004 08:09:07 +01:30", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+	{ "Sat, 06-Nov-2004 08:09:07 +0:180", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+	{ "Sat, 06-Nov-2004 08:09:07 +100:100", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+	{ "Sat, 06-Nov-2004 08:09:07 Z", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
 
 	/* rfc850-date, and broken variants */
 	{ "Saturday, 06-Nov-04 08:09:07 GMT", NULL },
@@ -109,6 +114,8 @@
 	{ "Sat Nov 06 08:09:07 2004", NULL },
 	{ "Sat Nov 6 08:09:07 2004", NULL },
 	{ "Sat Nov  6 08:09:07 2004 GMT", NULL },
+	{ "Sat Nov 6 08:09:07 2004 NoZone", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+	{ "Sat Nov 6 08:09:07 2004 UTC", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
 
 	/* Netscape cookie spec date, and broken variants */
 	{ "Sat, 06-Nov-2004 08:09:07 GMT", NULL },
@@ -182,7 +189,23 @@
 	{ "Sat Nov  6 08::07 2004", NULL },
 	{ "Sat Nov  6 08:09: 2004", NULL },
 	{ "Sat Nov  6 08:09:07", NULL },
-	{ "Sat Nov  6 08:09:07 GMT 2004", NULL }
+	{ "Sat Nov  6 08:09:07 GMT 2004", NULL },
+
+	/* range constraints added "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" */
+	{ "Sat, 00-Nov-2004 08:09:07 GMT", NULL },
+	{ "Sat, 32-Nov-2004 08:09:07 GMT", NULL },
+	{ "Sat, 06-Nov-0 08:09:07 GMT", NULL },
+	{ "Sat, 06-Nov-9999 08:09:07 GMT", NULL },
+	{ "Sat, 06-Nov-2004 0-1:09:07 GMT", NULL },
+	{ "(Sat), Nov  6 -1:09:07 2004", NULL },
+	{ "Sat, 06-Nov-2004 24:09:07 GMT", NULL },
+	{ "Sat, 06-Nov-2004 08:-1:07 GMT", NULL },
+	{ "Sat, 06-Nov-2004 08:60:07 GMT", NULL },
+	{ "Sat, 06-Nov-2004 08:09:-10 GMT", NULL },
+	{ "Sat, 06-Nov-2004 08:09:60 GMT", NULL },
+	{ "Sat, 06-Nov-71 08:09:99 UTC", NULL },
+	{ "Sat, 31-Feb-2004 08:09:07 UTC", NULL },
+	{ "2004-11-06T08:09:07Z", NULL }
 };
 
 static void
@@ -198,12 +221,12 @@
 	soup_test_assert (date == NULL,
 			  "date parsing succeeded for '%s': %d %d %d - %d %d %d",
 			  bad->date,
-                          g_date_time_get_year (date),
-                          g_date_time_get_month (date),
-                          g_date_time_get_day_of_month (date),
-                          g_date_time_get_hour (date),
-                          g_date_time_get_minute (date),
-                          g_date_time_get_second (date));
+			  g_date_time_get_year (date),
+			  g_date_time_get_month (date),
+			  g_date_time_get_day_of_month (date),
+			  g_date_time_get_hour (date),
+			  g_date_time_get_minute (date),
+			  g_date_time_get_second (date));
 	g_clear_pointer (&date, g_date_time_unref);
 }
 
diff -Nru libsoup3-3.6.5/tests/forms-test.c libsoup3-3.6.5/tests/forms-test.c
--- libsoup3-3.6.5/tests/forms-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/forms-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -485,6 +485,46 @@
 		soup_server_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED, NULL);
 }
 
+static void
+do_form_decode_multipart_test (void)
+{
+	SoupMultipart *multipart = soup_multipart_new ("multipart/form-data");
+	const char *file_control_name = "uploaded_file";
+	char *content_type = NULL;
+	char *filename = NULL;
+	GBytes *file = NULL;
+	GHashTable *result;
+	int part;
+
+	for (part = 0; part < 2; part++) {
+		SoupMessageHeaders *headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+		GHashTable *params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		GBytes *body = g_bytes_new (NULL, 0);
+
+		g_hash_table_insert (params, g_strdup ("name"), g_strdup (file_control_name));
+		g_hash_table_insert (params, g_strdup ("filename"), g_strdup (file_control_name));
+		soup_message_headers_set_content_disposition (headers, "form-data", params);
+		soup_message_headers_set_content_type (headers, "text/x-form", NULL);
+		soup_multipart_append_part (multipart, headers, body);
+
+		soup_message_headers_unref (headers);
+		g_hash_table_destroy (params);
+		g_bytes_unref (body);
+	}
+
+	/* this would leak memory of the output variables, due to two parts having the same 'file_control_name' */
+	result = soup_form_decode_multipart (multipart, file_control_name, &filename, &content_type, &file);
+	g_assert_nonnull (result);
+	g_assert_cmpstr (content_type, ==, "text/x-form");
+	g_assert_cmpstr (filename, ==, file_control_name);
+	g_assert_nonnull (file);
+
+	g_hash_table_destroy (result);
+	g_free (content_type);
+	g_free (filename);
+	g_bytes_unref (file);
+}
+
 static gboolean run_tests = TRUE;
 
 static GOptionEntry no_test_entry[] = {
@@ -525,6 +565,7 @@
 		g_uri_unref (uri);
 
 		g_test_add_func ("/forms/decode", do_form_decode_test);
+		g_test_add_func ("/forms/decodemultipart", do_form_decode_multipart_test);
 
 		ret = g_test_run ();
 	} else {
diff -Nru libsoup3-3.6.5/tests/http2-test.c libsoup3-3.6.5/tests/http2-test.c
--- libsoup3-3.6.5/tests/http2-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/http2-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -760,6 +760,7 @@
         SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
         soup_logger_set_printer (logger, log_printer, &has_logged_body, NULL);
         soup_session_add_feature (test->session, SOUP_SESSION_FEATURE (logger));
+        g_clear_object (&logger);
 
         uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL);
         msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri);
@@ -771,6 +772,7 @@
         g_assert_true (has_logged_body);
 
         g_bytes_unref (response);
+        g_bytes_unref (bytes);
         g_object_unref (msg);
         g_uri_unref (uri);
 }
@@ -1078,7 +1080,7 @@
 
         g_assert_nonnull (response);
         g_assert_no_error (error);
-        g_clear_error (&error);
+        g_bytes_unref (response);
         g_object_unref (msg);
         g_uri_unref (uri);
 }
@@ -1219,6 +1221,7 @@
         response = soup_test_session_async_send (test->session, msg, NULL, &error);
         g_assert_null (response);
         g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
+        g_clear_error (&error);
         g_object_unref (msg);
         g_uri_unref (uri);
 }
@@ -1241,6 +1244,30 @@
         g_uri_unref (uri);
 }
 
+static void
+do_broken_pseudo_header_test (Test *test, gconstpointer data)
+{
+	char *path;
+	SoupMessage *msg;
+	GUri *uri;
+	GBytes *body = NULL;
+	GError *error = NULL;
+
+	uri = g_uri_parse_relative (base_uri, "/ag", SOUP_HTTP_URI_FLAGS, NULL);
+
+	/* an ugly cheat to construct a broken URI, which can be sent from other libs */
+	path = (char *) g_uri_get_path (uri);
+	path[1] = '%';
+
+	msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+	body = soup_test_session_async_send (test->session, msg, NULL, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
+	g_assert_null (body);
+	g_clear_error (&error);
+	g_object_unref (msg);
+	g_uri_unref (uri);
+}
+
 static gboolean
 unpause_message (SoupServerMessage *msg)
 {
@@ -1294,10 +1321,14 @@
                 response_body = soup_server_message_get_response_body (msg);
                 for (i = 0; i < LARGE_N_CHARS; i++, letter++) {
                         GString *chunk = g_string_new (NULL);
+                        GBytes *bytes;
 
                         for (j = 0; j < LARGE_CHARS_REPEAT; j++)
                                 chunk = g_string_append_c (chunk, letter);
-                        soup_message_body_append_bytes (response_body, g_string_free_to_bytes (chunk));
+
+                        bytes = g_string_free_to_bytes (chunk);
+                        soup_message_body_append_bytes (response_body, bytes);
+                        g_bytes_unref (bytes);
                 }
                 soup_message_body_append (response_body, SOUP_MEMORY_STATIC, "\0", 1);
 
@@ -1549,6 +1580,10 @@
                     setup_session,
                     do_connection_closed_test,
                     teardown_session);
+        g_test_add ("/http2/broken-pseudo-header", Test, NULL,
+                    setup_session,
+                    do_broken_pseudo_header_test,
+                    teardown_session);
 
 	ret = g_test_run ();
 
diff -Nru libsoup3-3.6.5/tests/meson.build libsoup3-3.6.5/tests/meson.build
--- libsoup3-3.6.5/tests/meson.build	2025-07-12 12:28:15.000000000 +0100
+++ libsoup3-3.6.5/tests/meson.build	2025-07-12 12:28:16.000000000 +0100
@@ -95,7 +95,6 @@
   {'name': 'logger'},
   {'name': 'misc'},
   {'name': 'multipart'},
-  {'name': 'multithread'},
   {'name': 'no-ssl'},
   {'name': 'ntlm'},
   {'name': 'redirect'},
@@ -103,6 +102,7 @@
   {'name': 'samesite'},
   {'name': 'session'},
   {'name': 'server-auth'},
+  {'name': 'server-mem-limit'},
   {'name': 'server'},
   {'name': 'sniffing',
     'depends': [test_resources],
@@ -147,6 +147,7 @@
   tests += [
     {'name': 'auth', 'parallel': false},
     {'name': 'connection', 'parallel': false},
+    {'name': 'multithread', 'parallel': false},
     {'name': 'range', 'parallel': false},
     {'name': 'proxy', 'parallel': false},
   ]
diff -Nru libsoup3-3.6.5/tests/misc-test.c libsoup3-3.6.5/tests/misc-test.c
--- libsoup3-3.6.5/tests/misc-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/misc-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -156,6 +156,7 @@
 	stream = soup_session_send (session, msg, NULL, &error);
 	g_assert_null (stream);
 	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+	g_clear_error (&error);
 
 	soup_test_session_abort_unref (session);
 
@@ -846,6 +847,7 @@
         g_assert_nonnull (body);
         g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "index");
         g_object_unref (new_msg);
+        g_bytes_unref (body);
 }
 
 static void
diff -Nru libsoup3-3.6.5/tests/multipart-test.c libsoup3-3.6.5/tests/multipart-test.c
--- libsoup3-3.6.5/tests/multipart-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/multipart-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -471,6 +471,122 @@
 	loop = NULL;
 }
 
+static void
+test_multipart_bounds_good (void)
+{
+	#define TEXT "line1\r\nline2"
+	SoupMultipart *multipart;
+	SoupMessageHeaders *headers, *set_headers = NULL;
+	GBytes *bytes, *set_bytes = NULL;
+	const char *raw_data = "--123\r\nContent-Type: text/plain;\r\n\r\n" TEXT "\r\n--123--\r\n";
+	gboolean success;
+
+	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
+
+	bytes = g_bytes_new (raw_data, strlen (raw_data));
+
+	multipart = soup_multipart_new_from_message (headers, bytes);
+
+	g_assert_nonnull (multipart);
+	g_assert_cmpint (soup_multipart_get_length (multipart), ==, 1);
+	success = soup_multipart_get_part (multipart, 0, &set_headers, &set_bytes);
+	g_assert_true (success);
+	g_assert_nonnull (set_headers);
+	g_assert_nonnull (set_bytes);
+	g_assert_cmpint (strlen (TEXT), ==, g_bytes_get_size (set_bytes));
+	g_assert_cmpstr ("text/plain", ==, soup_message_headers_get_content_type (set_headers, NULL));
+	g_assert_cmpmem (TEXT, strlen (TEXT), g_bytes_get_data (set_bytes, NULL), g_bytes_get_size (set_bytes));
+
+	soup_message_headers_unref (headers);
+	g_bytes_unref (bytes);
+
+	soup_multipart_free (multipart);
+
+	#undef TEXT
+}
+
+static void
+test_multipart_bounds_bad (void)
+{
+	SoupMultipart *multipart;
+	SoupMessageHeaders *headers;
+	GBytes *bytes;
+	const char *raw_data = "--123\r\nContent-Type: text/plain;\r\nline1\r\nline2\r\n--123--\r\n";
+
+	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
+
+	bytes = g_bytes_new (raw_data, strlen (raw_data));
+
+	/* it did read out of raw_data/bytes bounds */
+	multipart = soup_multipart_new_from_message (headers, bytes);
+	g_assert_null (multipart);
+
+	soup_message_headers_unref (headers);
+	g_bytes_unref (bytes);
+}
+
+static void
+test_multipart_bounds_bad_2 (void)
+{
+	SoupMultipart *multipart;
+	SoupMessageHeaders *headers;
+	GBytes *bytes;
+	const char *raw_data = "\n--123\r\nline\r\n--123--\r";
+
+	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
+
+	bytes = g_bytes_new (raw_data, strlen (raw_data));
+
+	multipart = soup_multipart_new_from_message (headers, bytes);
+	g_assert_nonnull (multipart);
+
+	soup_multipart_free (multipart);
+	soup_message_headers_unref (headers);
+	g_bytes_unref (bytes);
+}
+
+static void
+test_multipart_too_large (void)
+{
+	const char *raw_body =
+		"-------------------\r\n"
+		"-\n"
+		"Cont\"\r\n"
+		"Content-Tynt----e:n\x8erQK\r\n"
+		"Content-Disposition:   name=  form-; name=\"file\"; filename=\"ype:i/  -d; ----\xae\r\n"
+		"Content-Typimag\x01/png--\\\n"
+		"\r\n"
+		"---:\n\r\n"
+		"\r\n"
+		"-------------------------------------\r\n"
+		"---------\r\n"
+		"----------------------";
+	GBytes *body;
+	GHashTable *params;
+	SoupMessageHeaders *headers;
+	SoupMultipart *multipart;
+
+	params = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (params, (gpointer) "boundary", (gpointer) "-----------------");
+	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	soup_message_headers_set_content_type (headers, "multipart/form-data", params);
+	g_hash_table_unref (params);
+
+	body = g_bytes_new_static (raw_body, strlen (raw_body));
+	multipart = soup_multipart_new_from_message (headers, body);
+	soup_message_headers_unref (headers);
+	g_bytes_unref (body);
+
+	g_assert_nonnull (multipart);
+	g_assert_cmpint (soup_multipart_get_length (multipart), ==, 1);
+	g_assert_true (soup_multipart_get_part (multipart, 0, &headers, &body));
+	g_assert_cmpint (g_bytes_get_size (body), ==, 0);
+	soup_multipart_free (multipart);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -498,6 +614,10 @@
 	g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart);
 	g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart);
 	g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
+	g_test_add_func ("/multipart/bounds-good", test_multipart_bounds_good);
+	g_test_add_func ("/multipart/bounds-bad", test_multipart_bounds_bad);
+	g_test_add_func ("/multipart/bounds-bad-2", test_multipart_bounds_bad_2);
+	g_test_add_func ("/multipart/too-large", test_multipart_too_large);
 
 	ret = g_test_run ();
 
diff -Nru libsoup3-3.6.5/tests/range-test.c libsoup3-3.6.5/tests/range-test.c
--- libsoup3-3.6.5/tests/range-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/range-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -82,6 +82,7 @@
 				debug_printf (1, "    Content-Range: %s\n", content_range);
 		}
 
+		g_clear_pointer (&body, g_bytes_unref);
 		g_object_unref (msg);
 		return;
 	}
diff -Nru libsoup3-3.6.5/tests/server-mem-limit-test.c libsoup3-3.6.5/tests/server-mem-limit-test.c
--- libsoup3-3.6.5/tests/server-mem-limit-test.c	1970-01-01 01:00:00.000000000 +0100
+++ libsoup3-3.6.5/tests/server-mem-limit-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -0,0 +1,149 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2025 Red Hat <www.redhat.com>
+ */
+
+#include "test-utils.h"
+
+#include <sys/resource.h>
+
+/*
+ This test limits memory usage to trigger too large buffer allocation crash.
+ As restoring the limits back to what it was does not always work, it's split
+ out of the server-test.c test with copied minimal server code.
+ */
+
+typedef struct {
+	SoupServer *server;
+	GUri *base_uri, *ssl_base_uri;
+	GSList *handlers;
+} ServerData;
+
+static void
+server_setup_nohandler (ServerData *sd, gconstpointer test_data)
+{
+	sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+	sd->base_uri = soup_test_server_get_uri (sd->server, "http", NULL);
+	if (tls_available)
+		sd->ssl_base_uri = soup_test_server_get_uri (sd->server, "https", NULL);
+}
+
+static void
+server_add_handler (ServerData         *sd,
+		    const char         *path,
+		    SoupServerCallback  callback,
+		    gpointer            user_data,
+		    GDestroyNotify      destroy)
+{
+	soup_server_add_handler (sd->server, path, callback, user_data, destroy);
+	sd->handlers = g_slist_prepend (sd->handlers, g_strdup (path));
+}
+
+static void
+server_setup (ServerData *sd, gconstpointer test_data)
+{
+	server_setup_nohandler (sd, test_data);
+}
+
+static void
+server_teardown (ServerData *sd, gconstpointer test_data)
+{
+	GSList *iter;
+
+	for (iter = sd->handlers; iter; iter = iter->next)
+		soup_server_remove_handler (sd->server, iter->data);
+	g_slist_free_full (sd->handlers, g_free);
+
+	g_clear_pointer (&sd->server, soup_test_server_quit_unref);
+	g_clear_pointer (&sd->base_uri, g_uri_unref);
+	g_clear_pointer (&sd->ssl_base_uri, g_uri_unref);
+}
+
+static void
+server_file_callback (SoupServer        *server,
+		      SoupServerMessage *msg,
+		      const char        *path,
+		      GHashTable        *query,
+		      gpointer           data)
+{
+	void *mem;
+
+	g_assert_cmpstr (path, ==, "/file");
+	g_assert_cmpstr (soup_server_message_get_method (msg), ==, SOUP_METHOD_GET);
+
+	mem = g_malloc0 (sizeof (char) * 1024 * 1024);
+	/* fedora-scan CI claims a warning about possibly leaked `mem` variable, thus use
+	   the copy and free it explicitly, to workaround the false positive; the g_steal_pointer()
+	   did not help for the malloc-ed memory */
+	soup_server_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_COPY, mem, sizeof (char) * 1024 *1024);
+	soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+	g_free (mem);
+}
+
+static void
+do_ranges_overlaps_test (ServerData *sd, gconstpointer test_data)
+{
+	SoupSession *session;
+	SoupMessage *msg;
+	GString *range;
+	GUri *uri;
+	const char *chunk = ",0,0,0,0,0,0,0,0,0,0,0";
+
+	g_test_bug ("428");
+
+	#ifdef G_OS_WIN32
+	g_test_skip ("Cannot run under windows");
+	return;
+	#endif
+
+	range = g_string_sized_new (99 * 1024);
+	g_string_append (range, "bytes=1024");
+	while (range->len < 99 * 1024)
+		g_string_append (range, chunk);
+
+	session = soup_test_session_new (NULL);
+	server_add_handler (sd, "/file", server_file_callback, NULL, NULL);
+
+	uri = g_uri_parse_relative (sd->base_uri, "/file", SOUP_HTTP_URI_FLAGS, NULL);
+
+	msg = soup_message_new_from_uri ("GET", uri);
+	soup_message_headers_append (soup_message_get_request_headers (msg), "Range", range->str);
+
+	soup_test_session_send_message (session, msg);
+
+	soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+
+	g_object_unref (msg);
+
+	g_string_free (range, TRUE);
+	g_uri_unref (uri);
+
+	soup_test_session_abort_unref (session);
+}
+
+int
+main (int argc, char **argv)
+{
+	int ret;
+
+	/* a build with an address sanitizer may crash on mmap() with the limit,
+	   thus skip the limit set in such case, even it may not necessarily
+	   trigger the bug if it regresses */
+	#if !defined(G_OS_WIN32) && !defined(B_SANITIZE_OPTION)
+	struct rlimit new_rlimit = { 1024UL * 1024UL * 1024UL * 2UL, 1024UL * 1024UL * 1024UL * 2UL };
+	/* limit memory usage, to trigger too large memory allocation abort */
+	g_assert_cmpint (setrlimit (RLIMIT_DATA, &new_rlimit), ==, 0);
+	#else
+	g_message ("server-mem-limit-test: Running without memory limit");
+	#endif
+
+	test_init (argc, argv, NULL);
+
+	g_test_add ("/server-mem/range-overlaps", ServerData, NULL,
+		    server_setup, do_ranges_overlaps_test, server_teardown);
+
+	ret = g_test_run ();
+
+	test_cleanup ();
+	return ret;
+}
diff -Nru libsoup3-3.6.5/tests/test-utils.c libsoup3-3.6.5/tests/test-utils.c
--- libsoup3-3.6.5/tests/test-utils.c	2025-07-12 12:28:15.000000000 +0100
+++ libsoup3-3.6.5/tests/test-utils.c	2025-07-12 12:28:16.000000000 +0100
@@ -143,6 +143,8 @@
 	va_start (args, format);
 	g_vprintf (format, args);
 	va_end (args);
+
+	fflush (stdout);
 }
 
 gboolean
@@ -625,9 +627,11 @@
 add_listener_in_thread (gpointer user_data)
 {
 	AddListenerData *data = user_data;
+	GUri *uri;
 
-	data->uri = add_listener (data->server, data->scheme, data->host);
+	uri = add_listener (data->server, data->scheme, data->host);
 	g_mutex_lock (&data->mutex);
+	data->uri = uri;
 	g_cond_signal (&data->cond);
 	g_mutex_unlock (&data->mutex);
 
@@ -659,9 +663,9 @@
 		data.host = host;
 		data.uri = NULL;
 
-		g_mutex_lock (&data.mutex);
 		soup_add_completion (context, add_listener_in_thread, &data);
 
+		g_mutex_lock (&data.mutex);
 		while (!data.uri)
 			g_cond_wait (&data.cond, &data.mutex);
 
diff -Nru libsoup3-3.6.5/tests/websocket-test.c libsoup3-3.6.5/tests/websocket-test.c
--- libsoup3-3.6.5/tests/websocket-test.c	2025-03-21 18:30:16.000000000 +0000
+++ libsoup3-3.6.5/tests/websocket-test.c	2025-07-12 12:28:16.000000000 +0100
@@ -1602,6 +1602,9 @@
 	g_clear_error (&error);
 	g_assert_null (received);
 
+	/* it can emit more errors while joining the thread, thus disconnect, to avoid memory leak */
+	g_signal_handlers_disconnect_by_func (test->client, G_CALLBACK (on_error_copy), &error);
+
         g_thread_join (thread);
 
 	WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
@@ -2050,6 +2053,9 @@
 	g_clear_error (&error);
 	g_assert_null (received);
 
+	/* it can emit more errors while joining the thread, thus disconnect, to avoid memory leak */
+	g_signal_handlers_disconnect_by_func (test->client, G_CALLBACK (on_error_copy), &error);
+
 	g_thread_join (thread);
 
 	WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
