debdiff libsoup3_3.2.2-2.dsc libsoup3_3.2.3-0+deb12u1.dsc | filterdiff -p1 -x'debian/patches/*.patch'

diff -Nru libsoup3-3.2.2/debian/changelog libsoup3-3.2.3/debian/changelog
--- libsoup3-3.2.2/debian/changelog	2023-03-01 19:57:12.000000000 +0000
+++ libsoup3-3.2.3/debian/changelog	2025-07-12 14:39:06.000000000 +0100
@@ -1,3 +1,88 @@
+libsoup3 (3.2.3-0+deb12u1) bookworm; urgency=medium
+
+  * Team upload
+
+  [ Jeremy Bícha ]
+  * d/control{,.in}: Add Build-Depends: ca-certificates for build-time tests
+    (Closes: #1064744, #1054962)
+
+  [ Simon McVittie ]
+  * Re-export patch series (no functional changes)
+  * New upstream old-stable release 3.2.3
+    - Fix a buffer overrun if asked to parse non-UTF-8 headers. It is
+      believed that this cannot happen on the client side, but it can
+      happen in SoupServer. (CVE-2024-52531, Closes: #1087417)
+    - Avoid an infinite loop in WebSocket processing which can cause a denial
+      of service via resource exhaustion (CVE-2024-52532, Closes: #1087416)
+    - Fix denial of service (crash) when parsing invalid data URLs
+      (CVE-2025-32051)
+    - Fix heap overflows during content sniffing
+      (CVE-2025-32052, libsoup3 equivalent of #1102214)
+      (CVE-2025-32053, libsoup3 equivalent of #1102215)
+    - Fix an integer overflow during parameter serialization
+      (CVE-2025-32050, libsoup3 equivalent of #1102212)
+  * Fix a regression introduced in 3.2.3 by backporting its fixes from
+    3.6.5:
+    - d/p/sniffer-Fix-potential-overflow.patch,
+      d/p/sniffer-Add-better-coverage-of-skip_insignificant_space.patch:
+      Fix more heap buffer overflows during content sniffing
+      (CVE-2025-2784; libsoup3 equivalent of #1102208)
+    - d/source/include-binaries: Configure dpkg to accept non-text diffs
+      in test data for CVE-2025-2784
+  * d/p/server-Add-note-about-recommended-usage.patch:
+    Update documentation to indicate the level of security support for
+    the server side.
+    Upstream clarified the documentation in 3.6.1 to state that SoupServer
+    is not intended to be exposed to untrusted clients.
+    (Related to CVE-2024-52531, CVE-2024-52532)
+  * d/p/tests-Add-test-for-passing-invalid-UTF-8-to-soup_header_p.patch:
+    Add test coverage related to CVE-2024-52531
+  * Backport additional CVE fixes from upstream release 3.5.2:
+    - d/p/headers-Strictly-don-t-allow-NUL-bytes.patch:
+      Reject HTTP headers if they contain NUL bytes
+      (CVE-2024-52530, libsoup3 equivalent of #1088812)
+  * Backport additional CVE fixes from upstream release 3.6.2:
+    - d/p/content-sniffer-Handle-sniffing-resource-shorter-than-4-b.patch:
+      Fix denial of service when sniffing type of a short resource
+      (CVE-2025-32909, libsoup3 equivalent of #1103517)
+    - d/p/auth-digest-Handle-missing-realm-in-authenticate-header.patch,
+      d/p/auth-digest-Handle-missing-nonce.patch,
+      d/p/auth-digest-Fix-leak.patch:
+      Fix denial of service (crash) during client-side authentication
+      (CVE-2025-32910, libsoup3 equivalent of #1103516)
+    - d/p/soup_message_headers_get_content_disposition-Fix-NULL-der.patch,
+      d/p/soup_message_headers_get_content_disposition-strdup-trunc.patch:
+      Fix memory management of message headers.
+      (CVE-2025-32911, CVE-2025-32913; libsoup3 equivalent of #1103515)
+    - d/p/soup_header_parse_quality_list-Fix-leak.patch:
+      Fix a memory leak (slow denial of service) in quality list parsing
+      (CVE-2025-46420, libsoup3 equivalent of #1104055)
+  * Backport additional CVE fixes from upstream release 3.6.5:
+    - d/p/auth-digest-Handle-missing-nonce-1.patch,
+      d/p/digest-auth-Handle-NULL-nonce.patch:
+      Fix additional denial of service issues related to CVE-2025-32910
+      (CVE-2025-32912, libsoup3 equivalent of #1103516)
+    - d/p/headers-Handle-parsing-edge-case.patch,
+      d/p/headers-Handle-parsing-only-newlines.patch:
+      Fix denial of service (crash) in http server header parsing
+      (CVE-2025-32906, libsoup3 equivalent of #1103521)
+    - d/p/session-Strip-authentication-credentails-on-cross-origin-.patch:
+      Fix credentials disclosure on cross-origin redirect
+      (CVE-2025-46421, libsoup3 equivalent of #110405)
+  * d/control: libsoup-3.0-tests Depends on ca-certificates
+    (Equivalent of #1054962, #1064744 for autopkgtests)
+  * d/p/connection-manager-don-t-crash-if-connection-outlives-its.patch:
+    Add patch from upstream fixing a use-after-free during disconnection.
+    In particular this resolves a hang during gnome-calculator startup,
+    when it downloads currency conversion data.
+    (Closes: #1077962, #1052551, #1098315, #1099119, #1100509, #1104456,
+    #1100541, #1101922, #1102471, #1059773)
+  * d/p/connection-auth-don-t-crash-if-connection-outlives-the-au.patch:
+    Add patch from upstream fixing another use-after-free during disconnect.
+    (Related to #1077962, etc.)
+
+ -- Simon McVittie <smcv@debian.org>  Sat, 12 Jul 2025 14:39:06 +0100
+
 libsoup3 (3.2.2-2) unstable; urgency=medium
 
   * Temporarily disable sysprof on hppa
diff -Nru libsoup3-3.2.2/debian/control libsoup3-3.2.3/debian/control
--- libsoup3-3.2.2/debian/control	2023-03-01 19:57:12.000000000 +0000
+++ libsoup3-3.2.3/debian/control	2025-07-12 14:39:06.000000000 +0100
@@ -8,6 +8,7 @@
 Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
 Uploaders: Jeremy Bicha <jbicha@ubuntu.com>
 Build-Depends: apache2 (>= 2.4) <!nocheck> <!noinsttest>,
+               ca-certificates <!nocheck> <!noinsttest>,
                curl <!nocheck> <!noinsttest>,
                debhelper-compat (= 13),
                dh-sequence-gir,
@@ -170,6 +171,7 @@
 Section: misc
 Architecture: any
 Depends: apache2 (>= 2.4),
+         ca-certificates,
          curl,
          libapache2-mod-php (<< 2:9),
          libapache2-mod-php (>= 2:7),
diff -Nru libsoup3-3.2.2/debian/control.in libsoup3-3.2.3/debian/control.in
--- libsoup3-3.2.2/debian/control.in	2023-03-01 19:57:12.000000000 +0000
+++ libsoup3-3.2.3/debian/control.in	2025-07-12 14:39:06.000000000 +0100
@@ -4,6 +4,7 @@
 Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
 Uploaders: @GNOME_TEAM@
 Build-Depends: apache2 (>= 2.4) <!nocheck> <!noinsttest>,
+               ca-certificates <!nocheck> <!noinsttest>,
                curl <!nocheck> <!noinsttest>,
                debhelper-compat (= 13),
                dh-sequence-gir,
@@ -166,6 +167,7 @@
 Section: misc
 Architecture: any
 Depends: apache2 (>= 2.4),
+         ca-certificates,
          curl,
          libapache2-mod-php (<< 2:9),
          libapache2-mod-php (>= 2:7),
diff -Nru libsoup3-3.2.2/debian/patches/series libsoup3-3.2.3/debian/patches/series
--- libsoup3-3.2.2/debian/patches/series	2023-03-01 19:57:12.000000000 +0000
+++ libsoup3-3.2.3/debian/patches/series	2025-07-12 14:39:06.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
+connection-manager-don-t-crash-if-connection-outlives-its.patch
+connection-auth-don-t-crash-if-connection-outlives-the-au.patch
+headers-Strictly-don-t-allow-NUL-bytes.patch
+tests-Add-test-for-passing-invalid-UTF-8-to-soup_header_p.patch
+server-Add-note-about-recommended-usage.patch
+sniffer-Fix-potential-overflow.patch
+sniffer-Add-better-coverage-of-skip_insignificant_space.patch
+content-sniffer-Handle-sniffing-resource-shorter-than-4-b.patch
+auth-digest-Handle-missing-realm-in-authenticate-header.patch
+auth-digest-Handle-missing-nonce.patch
+auth-digest-Fix-leak.patch
+soup_message_headers_get_content_disposition-Fix-NULL-der.patch
+soup_message_headers_get_content_disposition-strdup-trunc.patch
+soup_header_parse_quality_list-Fix-leak.patch
+auth-digest-Handle-missing-nonce-1.patch
+digest-auth-Handle-NULL-nonce.patch
+headers-Handle-parsing-edge-case.patch
+headers-Handle-parsing-only-newlines.patch
+session-Strip-authentication-credentails-on-cross-origin-.patch
Binary files /tmp/gXFt63eDKs/libsoup3-3.2.2/debian/patches/sniffer-Add-better-coverage-of-skip_insignificant_space.patch and /tmp/Sh13hCjkhM/libsoup3-3.2.3/debian/patches/sniffer-Add-better-coverage-of-skip_insignificant_space.patch differ
Binary files /tmp/gXFt63eDKs/libsoup3-3.2.2/debian/patches/sniffer-Fix-potential-overflow.patch and /tmp/Sh13hCjkhM/libsoup3-3.2.3/debian/patches/sniffer-Fix-potential-overflow.patch differ
diff -Nru libsoup3-3.2.2/debian/source/include-binaries libsoup3-3.2.3/debian/source/include-binaries
--- libsoup3-3.2.2/debian/source/include-binaries	1970-01-01 01:00:00.000000000 +0100
+++ libsoup3-3.2.3/debian/source/include-binaries	2025-07-12 14:39:06.000000000 +0100
@@ -0,0 +1,3 @@
+debian/patches/sniffer-Fix-potential-overflow.patch
+debian/patches/sniffer-Add-better-coverage-of-skip_insignificant_space.patch
+
diff -Nru libsoup3-3.2.2/debian/watch libsoup3-3.2.3/debian/watch
--- libsoup3-3.2.2/debian/watch	2023-03-01 19:57:12.000000000 +0000
+++ libsoup3-3.2.3/debian/watch	2025-07-12 14:39:06.000000000 +0100
@@ -1,4 +1,4 @@
 version=4
 opts="searchmode=plain, uversionmangle=s/\.(alpha|beta|rc)/~$1/, downloadurlmangle=s|cache.json||" \
 https://download.gnome.org/sources/libsoup/cache.json \
-	3[\d.]+[02468]/libsoup-([\d.]+)@ARCHIVE_EXT@
+	3.2/libsoup-([\d.]+)@ARCHIVE_EXT@
diff -Nru libsoup3-3.2.2/libsoup/auth/soup-auth-digest.c libsoup3-3.2.3/libsoup/auth/soup-auth-digest.c
--- libsoup3-3.2.2/libsoup/auth/soup-auth-digest.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/auth/soup-auth-digest.c	2025-07-12 14:43:24.000000000 +0100
@@ -72,6 +72,7 @@
 	g_free (priv->nonce);
 	g_free (priv->domain);
 	g_free (priv->cnonce);
+        g_free (priv->opaque);
 
 	memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
 	memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
@@ -139,6 +140,19 @@
 }
 
 static gboolean
+validate_params (SoupAuthDigest *auth_digest)
+{
+        SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+
+        if (priv->qop || priv->algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS) {
+                if (!priv->nonce)
+                        return FALSE;
+        }
+
+        return TRUE;
+}
+
+static gboolean
 soup_auth_digest_update (SoupAuth *auth, SoupMessage *msg,
 			 GHashTable *auth_params)
 {
@@ -148,6 +162,9 @@
 	guint qop_options;
 	gboolean ok = TRUE;
 
+        if (!soup_auth_get_realm (auth) || !g_hash_table_lookup (auth_params, "nonce"))
+                return FALSE;
+
 	g_free (priv->domain);
 	g_free (priv->nonce);
 	g_free (priv->opaque);
@@ -172,16 +189,21 @@
 	if (priv->algorithm == -1)
 		ok = FALSE;
 
-	stale = g_hash_table_lookup (auth_params, "stale");
-	if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp)
-		recompute_hex_a1 (priv);
-	else {
-		g_free (priv->user);
-		priv->user = NULL;
-		g_free (priv->cnonce);
-		priv->cnonce = NULL;
-		memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
-		memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
+        if (!validate_params (auth_digest))
+                ok = FALSE;
+
+        if (ok) {
+                stale = g_hash_table_lookup (auth_params, "stale");
+                if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp)
+                        recompute_hex_a1 (priv);
+                else {
+                        g_free (priv->user);
+                        priv->user = NULL;
+                        g_free (priv->cnonce);
+                        priv->cnonce = NULL;
+                        memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
+                        memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
+                }
         }
 
 	return ok;
@@ -273,6 +295,8 @@
 
 		/* In MD5-sess, A1 is hex_urp:nonce:cnonce */
 
+                g_assert (nonce && cnonce);
+
 		checksum = g_checksum_new (G_CHECKSUM_MD5);
 		g_checksum_update (checksum, (guchar *)hex_urp, strlen (hex_urp));
 		g_checksum_update (checksum, (guchar *)":", 1);
@@ -363,6 +387,8 @@
 	if (qop) {
 		char tmp[9];
 
+                g_assert (cnonce);
+
 		g_snprintf (tmp, 9, "%.8x", nc);
 		g_checksum_update (checksum, (guchar *)tmp, strlen (tmp));
 		g_checksum_update (checksum, (guchar *)":", 1);
@@ -426,6 +452,9 @@
 	g_return_val_if_fail (uri != NULL, NULL);
 	url = soup_uri_get_path_and_query (uri);
 
+        g_assert (priv->nonce);
+        g_assert (!priv->qop || priv->cnonce);
+
 	soup_auth_digest_compute_response (soup_message_get_method (msg), url, priv->hex_a1,
 					   priv->qop, priv->nonce,
 					   priv->cnonce, priv->nc,
diff -Nru libsoup3-3.2.2/libsoup/auth/soup-connection-auth.c libsoup3-3.2.3/libsoup/auth/soup-connection-auth.c
--- libsoup3-3.2.2/libsoup/auth/soup-connection-auth.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/auth/soup-connection-auth.c	2025-07-12 14:43:24.000000000 +0100
@@ -103,8 +103,8 @@
 	if (!state) {
                 state = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->create_connection_state (auth);
                 if (conn) {
-                        g_signal_connect (conn, "disconnected",
-                                          G_CALLBACK (connection_disconnected), auth);
+                        g_signal_connect_object (conn, "disconnected",
+                                                 G_CALLBACK (connection_disconnected), auth, 0);
                 }
 
                 g_hash_table_insert (priv->conns, conn, state);
diff -Nru libsoup3-3.2.2/libsoup/content-sniffer/soup-content-sniffer.c libsoup3-3.2.3/libsoup/content-sniffer/soup-content-sniffer.c
--- libsoup3-3.2.2/libsoup/content-sniffer/soup-content-sniffer.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/content-sniffer/soup-content-sniffer.c	2025-07-12 14:43:24.000000000 +0100
@@ -234,9 +234,14 @@
 	gsize resource_length;
 	const char *resource = g_bytes_get_data (buffer, &resource_length);
 	resource_length = MIN (512, resource_length);
-	guint32 box_size = *((guint32*)resource);
+	guint32 box_size;
 	guint i;
 
+        if (resource_length < sizeof (guint32))
+                return FALSE;
+
+	box_size = *((guint32*)resource);
+
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 	box_size = ((box_size >> 24) |
 		    ((box_size << 8) & 0x00FF0000) |
@@ -515,7 +520,7 @@
 			guint index_pattern = 0;
 			gboolean skip_row = FALSE;
 
-			while ((index_stream < resource_length) &&
+			while ((index_stream < resource_length - 1) &&
 			       (index_pattern <= type_row->pattern_length)) {
 				/* Skip insignificant white space ("WS" in the spec) */
 				if (type_row->pattern[index_pattern] == ' ') {
@@ -624,15 +629,18 @@
 }
 
 static gboolean
-skip_insignificant_space (const char *resource, int *pos, int resource_length)
+skip_insignificant_space (const char *resource, gsize *pos, gsize resource_length)
 {
+        if (*pos >= resource_length)
+	        return TRUE;
+
 	while ((resource[*pos] == '\x09') ||
 	       (resource[*pos] == '\x20') ||
 	       (resource[*pos] == '\x0A') ||
 	       (resource[*pos] == '\x0D')) {
 		*pos = *pos + 1;
 
-		if (*pos > resource_length)
+		if (*pos >= resource_length)
 			return TRUE;
 	}
 
@@ -645,7 +653,7 @@
 	gsize resource_length;
 	const char *resource = g_bytes_get_data (buffer, &resource_length);
 	resource_length = MIN (512, resource_length);
-	int pos = 0;
+	gsize pos = 0;
 
 	if (resource_length < 3)
 		goto text_html;
@@ -655,9 +663,6 @@
 		pos = 3;
 
  look_for_tag:
-	if (pos > resource_length)
-		goto text_html;
-
 	if (skip_insignificant_space (resource, &pos, resource_length))
 		goto text_html;
 
@@ -695,7 +700,7 @@
 		do {
 			pos++;
 
-			if (pos > resource_length)
+			if ((pos + 1) > resource_length)
 				goto text_html;
 		} while (resource[pos] != '>');
 
diff -Nru libsoup3-3.2.2/libsoup/server/soup-server.c libsoup3-3.2.3/libsoup/server/soup-server.c
--- libsoup3-3.2.2/libsoup/server/soup-server.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/server/soup-server.c	2025-07-12 14:43:24.000000000 +0100
@@ -28,9 +28,11 @@
 /**
  * SoupServer:
  *
- * A HTTP server.
- *
- * #SoupServer implements a simple HTTP server.
+ * #SoupServer provides a basic implementation of an HTTP server. The
+ * recommended usage of this server is for internal use, tasks like
+ * a mock server for tests, a private service for IPC, etc. It is not
+ * recommended to be exposed to untrusted clients as it may be vulnerable
+ * to denial of service attacks or other exploits.
  *
  * To begin, create a server using [ctor@Server.new]. Add at least one
  * handler by calling [method@Server.add_handler] or
diff -Nru libsoup3-3.2.2/libsoup/soup-connection-manager.c libsoup3-3.2.3/libsoup/soup-connection-manager.c
--- libsoup3-3.2.2/libsoup/soup-connection-manager.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/soup-connection-manager.c	2025-07-12 14:43:24.000000000 +0100
@@ -183,6 +183,26 @@
         return host;
 }
 
+static void
+soup_connection_manager_drop_connection (SoupConnectionManager *manager,
+                                         SoupConnection        *conn)
+{
+        g_signal_handlers_disconnect_by_data (conn, manager);
+        manager->num_conns--;
+        g_object_unref (conn);
+
+        g_cond_broadcast (&manager->cond);
+}
+
+static void
+remove_connection (gpointer key,
+                   gpointer value,
+                   gpointer user_data)
+{
+        SoupConnectionManager *manager = user_data;
+        soup_connection_manager_drop_connection (manager, key);
+}
+
 SoupConnectionManager *
 soup_connection_manager_new (SoupSession *session,
                              guint        max_conns,
@@ -212,6 +232,9 @@
 void
 soup_connection_manager_free (SoupConnectionManager *manager)
 {
+        g_hash_table_foreach (manager->conns, remove_connection, manager);
+        g_assert (manager->num_conns == 0);
+
         g_clear_object (&manager->remote_connectable);
         g_hash_table_destroy (manager->http_hosts);
         g_hash_table_destroy (manager->https_hosts);
@@ -271,17 +294,6 @@
 }
 
 static void
-soup_connection_manager_drop_connection (SoupConnectionManager *manager,
-                                         SoupConnection        *conn)
-{
-        g_signal_handlers_disconnect_by_data (conn, manager);
-        manager->num_conns--;
-        g_object_unref (conn);
-
-        g_cond_broadcast (&manager->cond);
-}
-
-static void
 soup_connection_list_disconnect_all (GList *conns)
 {
         GList *c;
diff -Nru libsoup3-3.2.2/libsoup/soup-headers.c libsoup3-3.2.3/libsoup/soup-headers.c
--- libsoup3-3.2.2/libsoup/soup-headers.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/soup-headers.c	2025-07-12 14:43:24.000000000 +0100
@@ -51,13 +51,14 @@
 	 * ignorable trailing whitespace.
 	 */
 
+	/* No '\0's are allowed */
+	if (memchr (str, '\0', len))
+		return FALSE;
+
 	/* Skip over the Request-Line / Status-Line */
 	headers_start = memchr (str, '\n', len);
 	if (!headers_start)
 		return FALSE;
-	/* No '\0's in the Request-Line / Status-Line */
-	if (memchr (str, '\0', headers_start - str))
-		return FALSE;
 
 	/* We work on a copy of the headers, which we can write '\0's
 	 * into, so that we don't have to individually g_strndup and
@@ -69,14 +70,6 @@
 	headers_copy[copy_len] = '\0';
 	value_end = headers_copy;
 
-	/* There shouldn't be any '\0's in the headers already, but
-	 * this is the web we're talking about.
-	 */
-	while ((p = memchr (headers_copy, '\0', copy_len))) {
-		memmove (p, p + 1, copy_len - (p - headers_copy));
-		copy_len--;
-	}
-
 	while (*(value_end + 1)) {
 		name = value_end + 1;
 		name_end = strchr (name, ':');
@@ -193,7 +186,7 @@
 	/* RFC 2616 4.1 "servers SHOULD ignore any empty line(s)
 	 * received where a Request-Line is expected."
 	 */
-	while ((*str == '\r' || *str == '\n') && len > 0) {
+	while (len > 0 && (*str == '\r' || *str == '\n')) {
 		str++;
 		len--;
 	}
@@ -232,7 +225,7 @@
 	    !g_ascii_isdigit (version[5]))
 		return SOUP_STATUS_BAD_REQUEST;
 	major_version = strtoul (version + 5, &p, 10);
-	if (*p != '.' || !g_ascii_isdigit (p[1]))
+	if (p + 1 >= str + len || *p != '.' || !g_ascii_isdigit (p[1]))
 		return SOUP_STATUS_BAD_REQUEST;
 	minor_version = strtoul (p + 1, &p, 10);
 	version_end = p;
@@ -378,7 +371,7 @@
 	 * after a response, which we then see prepended to the next
 	 * response on that connection.
 	 */
-	while ((*str == '\r' || *str == '\n') && len > 0) {
+	while (len > 0 && (*str == '\r' || *str == '\n')) {
 		str++;
 		len--;
 	}
@@ -537,7 +530,7 @@
 	GSList *unsorted;
 	QualityItem *array;
 	GSList *sorted, *iter;
-	char *item, *semi;
+	char *semi;
 	const char *param, *equal, *value;
 	double qval;
 	int n;
@@ -550,9 +543,8 @@
 	unsorted = soup_header_parse_list (header);
 	array = g_new0 (QualityItem, g_slist_length (unsorted));
 	for (iter = unsorted, n = 0; iter; iter = iter->next) {
-		item = iter->data;
 		qval = 1.0;
-		for (semi = strchr (item, ';'); semi; semi = strchr (semi + 1, ';')) {
+		for (semi = strchr (iter->data, ';'); semi; semi = strchr (semi + 1, ';')) {
 			param = skip_lws (semi + 1);
 			if (*param != 'q')
 				continue;
@@ -584,15 +576,15 @@
 		if (qval == 0.0) {
 			if (unacceptable) {
 				*unacceptable = g_slist_prepend (*unacceptable,
-								 item);
+								 g_steal_pointer (&iter->data));
 			}
 		} else {
-			array[n].item = item;
+			array[n].item = g_steal_pointer (&iter->data);
 			array[n].qval = qval;
 			n++;
 		}
 	}
-	g_slist_free (unsorted);
+	g_slist_free_full (unsorted, g_free);
 
 	qsort (array, n, sizeof (QualityItem), sort_by_qval);
 	sorted = NULL;
@@ -653,8 +645,9 @@
 }
 
 static void
-decode_quoted_string (char *quoted_string)
+decode_quoted_string_inplace (GString *quoted_gstring)
 {
+	char *quoted_string = quoted_gstring->str;
 	char *src, *dst;
 
 	src = quoted_string + 1;
@@ -668,10 +661,11 @@
 }
 
 static gboolean
-decode_rfc5987 (char *encoded_string)
+decode_rfc5987_inplace (GString *encoded_gstring)
 {
 	char *q, *decoded;
 	gboolean iso_8859_1 = FALSE;
+	const char *encoded_string = encoded_gstring->str;
 
 	q = strchr (encoded_string, '\'');
 	if (!q)
@@ -703,14 +697,7 @@
 		decoded = utf8;
 	}
 
-	/* If encoded_string was UTF-8, then each 3-character %-escape
-	 * will be converted to a single byte, and so decoded is
-	 * shorter than encoded_string. If encoded_string was
-	 * iso-8859-1, then each 3-character %-escape will be
-	 * converted into at most 2 bytes in UTF-8, and so it's still
-	 * shorter.
-	 */
-	strcpy (encoded_string, decoded);
+	g_string_assign (encoded_gstring, decoded);
 	g_free (decoded);
 	return TRUE;
 }
@@ -720,15 +707,17 @@
 {
 	GHashTable *params;
 	GSList *list, *iter;
-	char *item, *eq, *name_end, *value;
-	gboolean override, duplicated;
 
 	params = g_hash_table_new_full (soup_str_case_hash, 
 					soup_str_case_equal,
-					g_free, NULL);
+					g_free, g_free);
 
 	list = parse_list (header, delim);
 	for (iter = list; iter; iter = iter->next) {
+		char *item, *eq, *name_end;
+		gboolean override, duplicated;
+		GString *parsed_value = NULL;
+
 		item = iter->data;
 		override = FALSE;
 
@@ -743,19 +732,19 @@
 
 			*name_end = '\0';
 
-			value = (char *)skip_lws (eq + 1);
+			parsed_value = g_string_new ((char *)skip_lws (eq + 1));
 
 			if (name_end[-1] == '*' && name_end > item + 1) {
 				name_end[-1] = '\0';
-				if (!decode_rfc5987 (value)) {
+				if (!decode_rfc5987_inplace (parsed_value)) {
+					g_string_free (parsed_value, TRUE);
 					g_free (item);
 					continue;
 				}
 				override = TRUE;
-			} else if (*value == '"')
-				decode_quoted_string (value);
-		} else
-			value = NULL;
+			} else if (parsed_value->str[0] == '"')
+				decode_quoted_string_inplace (parsed_value);
+		}
 
 		duplicated = g_hash_table_lookup_extended (params, item, NULL, NULL);
 
@@ -763,11 +752,16 @@
 			soup_header_free_param_list (params);
 			params = NULL;
 			g_slist_foreach (iter, (GFunc)g_free, NULL);
+			if (parsed_value)
+				g_string_free (parsed_value, TRUE);
 			break;
-		} else if (override || !duplicated)
-			g_hash_table_replace (params, item, value);
-		else
+		} else if (override || !duplicated) {
+			g_hash_table_replace (params, item, parsed_value ? g_string_free (parsed_value, FALSE) : NULL);
+		} else {
+			if (parsed_value)
+				g_string_free (parsed_value, TRUE);
 			g_free (item);
+		}
 	}
 
 	g_slist_free (list);
@@ -912,7 +906,7 @@
 		     const char *name,
 		     const char *value)
 {
-	int len;
+	gsize len;
 
 	g_string_append (string, name);
 	g_string_append (string, "=\"");
diff -Nru libsoup3-3.2.2/libsoup/soup-message-headers.c libsoup3-3.2.3/libsoup/soup-message-headers.c
--- libsoup3-3.2.2/libsoup/soup-message-headers.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/soup-message-headers.c	2025-07-12 14:43:24.000000000 +0100
@@ -1608,10 +1608,15 @@
 	 */
 	if (params && g_hash_table_lookup_extended (*params, "filename",
 						    &orig_key, &orig_value)) {
-		char *filename = strrchr (orig_value, '/');
+                if (orig_value) {
+                        char *filename = strrchr (orig_value, '/');
 
-		if (filename)
-			g_hash_table_insert (*params, g_strdup (orig_key), filename + 1);
+                        if (filename)
+                                g_hash_table_insert (*params, g_strdup (orig_key), g_strdup (filename + 1));
+                } else {
+                        /* filename with no value isn't valid. */
+                        g_hash_table_remove (*params, "filename");
+                }
 	}
 	return TRUE;
 }
diff -Nru libsoup3-3.2.2/libsoup/soup-session.c libsoup3-3.2.3/libsoup/soup-session.c
--- libsoup3-3.2.2/libsoup/soup-session.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/soup-session.c	2025-07-12 14:43:24.000000000 +0100
@@ -1230,6 +1230,12 @@
 						   SOUP_ENCODING_NONE);
 	}
 
+        /* Strip all credentials on cross-origin redirect. */
+        if (!soup_uri_host_equal (soup_message_get_uri (msg), new_uri)) {
+                soup_message_headers_remove_common (soup_message_get_request_headers (msg), SOUP_HEADER_AUTHORIZATION);
+                soup_message_set_auth (msg, NULL);
+        }
+
         soup_message_set_request_host_from_uri (msg, new_uri);
 	soup_message_set_uri (msg, new_uri);
 	g_uri_unref (new_uri);
diff -Nru libsoup3-3.2.2/libsoup/soup-uri-utils.c libsoup3-3.2.3/libsoup/soup-uri-utils.c
--- libsoup3-3.2.2/libsoup/soup-uri-utils.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/soup-uri-utils.c	2024-11-25 00:39:07.000000000 +0000
@@ -286,6 +286,7 @@
         gboolean base64 = FALSE;
         char *uri_string;
         GBytes *bytes;
+        const char *path;
 
         g_return_val_if_fail (uri != NULL, NULL);
 
@@ -301,8 +302,17 @@
         if (content_type)
                 *content_type = NULL;
 
+        /* g_uri_to_string() is picky about paths that start with `//` and will assert. */
+        path = g_uri_get_path (soup_uri);
+        if (path[0] == '/' && path[1] == '/') {
+                g_uri_unref (soup_uri);
+                return NULL;
+        }
+
         uri_string = g_uri_to_string (soup_uri);
         g_uri_unref (soup_uri);
+        if (!uri_string)
+                return NULL;
 
         start = uri_string + 5;
         comma = strchr (start, ',');
diff -Nru libsoup3-3.2.2/libsoup/websocket/soup-websocket-connection.c libsoup3-3.2.3/libsoup/websocket/soup-websocket-connection.c
--- libsoup3-3.2.2/libsoup/websocket/soup-websocket-connection.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/libsoup/websocket/soup-websocket-connection.c	2024-11-25 00:39:07.000000000 +0000
@@ -1150,9 +1150,9 @@
 		}
 
 		priv->incoming->len = len + count;
-	} while (count > 0);
 
-	process_incoming (self);
+		process_incoming (self);
+	} while (count > 0 && !priv->close_sent && !priv->io_closing);
 
 	if (end) {
 		if (!priv->close_sent || !priv->close_received) {
diff -Nru libsoup3-3.2.2/meson.build libsoup3-3.2.3/meson.build
--- libsoup3-3.2.2/meson.build	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/meson.build	2024-11-25 00:39:07.000000000 +0000
@@ -1,5 +1,5 @@
 project('libsoup', 'c',
-        version: '3.2.2',
+        version: '3.2.3',
         meson_version : '>= 0.54',
         license : 'LGPL-2.0-or-later',
         default_options : [
diff -Nru libsoup3-3.2.2/NEWS libsoup3-3.2.3/NEWS
--- libsoup3-3.2.2/NEWS	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/NEWS	2024-11-25 00:39:07.000000000 +0000
@@ -1,3 +1,11 @@
+Changes in libsoup from 3.2.2 to 3.2.3:
+
+* Fix possible NULL deref in `soup_uri_decode_data_uri()` [Ar Jun]
+* Fix possible overflow in `SoupContentSniffer` [Ar Jun]
+* Fix assertion in `soup_uri_decode_data_uri()` on URLs with a path starting with `//` [Patrick Griffis]
+* websocket: Fix possibility of being stuck in a read loop [Ignacio Casal Quinteiro]
+
+
 Changes in libsoup from 3.2.1 to 3.2.2:
 
 * Various HTTP/2 Fixes: [Carlos Garcia Campos]
diff -Nru libsoup3-3.2.2/tests/auth-test.c libsoup3-3.2.3/tests/auth-test.c
--- libsoup3-3.2.2/tests/auth-test.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/tests/auth-test.c	2025-07-12 14:43:24.000000000 +0100
@@ -1,6 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
 
 #include "test-utils.h"
+#include "soup-uri-utils-private.h"
 
 static const char *base_uri;
 static GMainLoop *loop;
@@ -1866,6 +1867,131 @@
 	soup_test_server_quit_unref (server);
 }
 
+static void
+on_request_read_for_missing_params (SoupServer        *server,
+                                      SoupServerMessage *msg,
+                                      gpointer           user_data)
+{
+        const char *auth_header = user_data;
+        SoupMessageHeaders *response_headers = soup_server_message_get_response_headers (msg);
+        soup_message_headers_replace (response_headers, "WWW-Authenticate", auth_header);
+}
+
+static void
+do_missing_params_test (gconstpointer auth_header)
+{
+        SoupSession *session;
+        SoupMessage *msg;
+        SoupServer *server;
+        SoupAuthDomain *digest_auth_domain;
+        gint status;
+        GUri *uri;
+
+        server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+	soup_server_add_handler (server, NULL,
+				 server_callback, NULL, NULL);
+	uri = soup_test_server_get_uri (server, "http", NULL);
+
+	digest_auth_domain = soup_auth_domain_digest_new (
+		"realm", "auth-test",
+		"auth-callback", server_digest_auth_callback,
+		NULL);
+        soup_auth_domain_add_path (digest_auth_domain, "/");
+	soup_server_add_auth_domain (server, digest_auth_domain);
+        g_object_unref (digest_auth_domain);
+
+        g_signal_connect (server, "request-read",
+                          G_CALLBACK (on_request_read_for_missing_params),
+                          (gpointer)auth_header);
+
+        session = soup_test_session_new (NULL);
+        msg = soup_message_new_from_uri ("GET", uri);
+        g_signal_connect (msg, "authenticate",
+                          G_CALLBACK (on_digest_authenticate),
+                          NULL);
+
+        status = soup_test_session_send_message (session, msg);
+
+        g_assert_cmpint (status, ==, SOUP_STATUS_UNAUTHORIZED);
+	g_uri_unref (uri);
+	soup_test_server_quit_unref (server);
+}
+
+static void
+redirect_server_callback (SoupServer        *server,
+                          SoupServerMessage *msg,
+                          const char        *path,
+                          GHashTable        *query,
+                          gpointer           user_data)
+{
+    static gboolean redirected = FALSE;
+
+    if (!redirected) {
+        char *redirect_uri = g_uri_to_string (user_data);
+        soup_server_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, redirect_uri);
+        g_free (redirect_uri);
+        redirected = TRUE;
+        return;
+    }
+
+    g_assert_not_reached ();
+}
+
+static gboolean
+auth_for_redirect_callback (SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data)
+{
+    GUri *known_server_uri = user_data;
+
+    if (!soup_uri_host_equal (known_server_uri, soup_message_get_uri (msg)))
+        return FALSE;
+
+    soup_auth_authenticate (auth, "user", "good-basic");
+
+    return TRUE;
+}
+
+static void
+do_strip_on_crossorigin_redirect (void)
+{
+    SoupSession *session;
+    SoupMessage *msg;
+    SoupServer *server1, *server2;
+    SoupAuthDomain *auth_domain;
+    GUri *uri;
+    gint status;
+
+    server1 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+    server2 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+
+    /* Both servers have the same credentials. */
+    auth_domain = soup_auth_domain_basic_new ("realm", "auth-test", "auth-callback", server_basic_auth_callback, NULL);
+    soup_auth_domain_add_path (auth_domain, "/");
+    soup_server_add_auth_domain (server1, auth_domain);
+    soup_server_add_auth_domain (server2, auth_domain);
+    g_object_unref (auth_domain);
+
+    /* Server 1 asks for auth, then redirects to Server 2. */
+    soup_server_add_handler (server1, NULL,
+                    redirect_server_callback,
+                   soup_test_server_get_uri (server2, "http", NULL), (GDestroyNotify)g_uri_unref);
+    /* Server 2 requires auth. */
+    soup_server_add_handler (server2, NULL, server_callback, NULL, NULL);
+
+    session = soup_test_session_new (NULL);
+    uri = soup_test_server_get_uri (server1, "http", NULL);
+    msg = soup_message_new_from_uri ("GET", uri);
+    /* The client only sends credentials for the host it knows. */
+    g_signal_connect (msg, "authenticate", G_CALLBACK (auth_for_redirect_callback), uri);
+
+    status = soup_test_session_send_message (session, msg);
+
+    g_assert_cmpint (status, ==, SOUP_STATUS_UNAUTHORIZED);
+
+    g_uri_unref (uri);
+    soup_test_server_quit_unref (server1);
+    soup_test_server_quit_unref (server2);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -1899,6 +2025,11 @@
 	g_test_add_func ("/auth/auth-uri", do_auth_uri_test);
         g_test_add_func ("/auth/cancel-request-on-authenticate", do_cancel_request_on_authenticate);
         g_test_add_func ("/auth/multiple-algorithms", do_multiple_digest_algorithms);
+        g_test_add_func ("/auth/strip-on-crossorigin-redirect", do_strip_on_crossorigin_redirect);
+        g_test_add_data_func ("/auth/missing-params/realm", "Digest qop=\"auth\"", do_missing_params_test);
+        g_test_add_data_func ("/auth/missing-params/nonce", "Digest realm=\"auth-test\", qop=\"auth,auth-int\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"", do_missing_params_test);
+        g_test_add_data_func ("/auth/missing-params/nonce-md5-sess", "Digest realm=\"auth-test\", qop=\"auth,auth-int\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\" algorithm=\"MD5-sess\"", do_missing_params_test);
+        g_test_add_data_func ("/auth/missing-params/nonce-and-qop", "Digest realm=\"auth-test\"", do_missing_params_test);
 
 	ret = g_test_run ();
 
diff -Nru libsoup3-3.2.2/tests/header-parsing-test.c libsoup3-3.2.3/tests/header-parsing-test.c
--- libsoup3-3.2.2/tests/header-parsing-test.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/tests/header-parsing-test.c	2025-07-12 14:43:24.000000000 +0100
@@ -6,6 +6,15 @@
 	const char *name, *value;
 } Header;
 
+/* These are not C strings to ensure going one byte over is not safe. */
+static char unterminated_http_version[] = {
+        'G','E','T',' ','/',' ','H','T','T','P','/','1', '0', '0', '.'
+};
+
+static char only_newlines[] = {
+        '\n', '\n', '\n', '\n'
+};
+
 static struct RequestTest {
 	const char *description;
 	const char *bugref;
@@ -358,24 +367,6 @@
 	  }
 	},
 
-	{ "NUL in header name", "760832",
-	  "GET / HTTP/1.1\r\nHost\x00: example.com\r\n", 36,
-	  SOUP_STATUS_OK,
-	  "GET", "/", SOUP_HTTP_1_1,
-	  { { "Host", "example.com" },
-	    { NULL }
-	  }
-	},
-
-	{ "NUL in header value", "760832",
-	  "GET / HTTP/1.1\r\nHost: example\x00" "com\r\n", 35,
-	  SOUP_STATUS_OK,
-	  "GET", "/", SOUP_HTTP_1_1,
-	  { { "Host", "examplecom" },
-	    { NULL }
-	  }
-	},
-
 	/************************/
 	/*** INVALID REQUESTS ***/
 	/************************/
@@ -401,6 +392,13 @@
 	  { { NULL } }
 	},
 
+	{ "Long HTTP version terminating at missing minor version", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/404",
+	  unterminated_http_version, sizeof (unterminated_http_version),
+	  SOUP_STATUS_BAD_REQUEST,
+           NULL, NULL, -1,
+	  { { NULL } }
+	},
+
 	{ "Non-HTTP request", NULL,
 	  "GET / SOUP/1.1\r\nHost: example.com\r\n", -1,
 	  SOUP_STATUS_BAD_REQUEST,
@@ -448,6 +446,28 @@
 	  SOUP_STATUS_EXPECTATION_FAILED,
 	  NULL, NULL, -1,
 	  { { NULL } }
+	},
+
+	// https://gitlab.gnome.org/GNOME/libsoup/-/issues/377
+	{ "NUL in header name", NULL,
+	  "GET / HTTP/1.1\r\nHost\x00: example.com\r\n", 36,
+	  SOUP_STATUS_BAD_REQUEST,
+	  NULL, NULL, -1,
+	  { { NULL } }
+	},
+
+	{ "NUL in header value", NULL,
+	  "HTTP/1.1 200 OK\r\nFoo: b\x00" "ar\r\n", 28,
+	  SOUP_STATUS_BAD_REQUEST,
+           NULL, NULL, -1,
+	  { { NULL } }
+	},
+
+	{ "Only newlines", NULL,
+	  only_newlines, sizeof (only_newlines),
+	  SOUP_STATUS_BAD_REQUEST,
+           NULL, NULL, -1,
+	  { { NULL } }
 	}
 };
 static const int num_reqtests = G_N_ELEMENTS (reqtests);
@@ -620,22 +640,6 @@
 	    { NULL } }
 	},
 
-	{ "NUL in header name", "760832",
-	  "HTTP/1.1 200 OK\r\nF\x00oo: bar\r\n", 28,
-	  SOUP_HTTP_1_1, SOUP_STATUS_OK, "OK",
-	  { { "Foo", "bar" },
-	    { NULL }
-	  }
-	},
-
-	{ "NUL in header value", "760832",
-	  "HTTP/1.1 200 OK\r\nFoo: b\x00" "ar\r\n", 28,
-	  SOUP_HTTP_1_1, SOUP_STATUS_OK, "OK",
-	  { { "Foo", "bar" },
-	    { NULL }
-	  }
-	},
-
 	/********************************/
 	/*** VALID CONTINUE RESPONSES ***/
 	/********************************/
@@ -768,6 +772,19 @@
 	  { { NULL }
 	  }
 	},
+
+	// https://gitlab.gnome.org/GNOME/libsoup/-/issues/377
+	{ "NUL in header name", NULL,
+	  "HTTP/1.1 200 OK\r\nF\x00oo: bar\r\n", 28,
+	  -1, 0, NULL,
+	  { { NULL } }
+	},
+
+	{ "NUL in header value", "760832",
+	  "HTTP/1.1 200 OK\r\nFoo: b\x00" "ar\r\n", 28,
+	  -1, 0, NULL,
+	  { { NULL } }
+	},
 };
 static const int num_resptests = G_N_ELEMENTS (resptests);
 
@@ -831,6 +848,17 @@
 	    { "filename", "t\xC3\xA9st.txt" },
 	  },
 	},
+
+        /* This tests invalid UTF-8 data which *should* never be passed here but it was designed to be robust against it. */
+        { TRUE,
+              "invalid*=\x69\x27\x27\x93\x93\x93\x93\xff\x61\x61\x61\x61\x61\x61\x61\x62\x63\x64\x65\x0a; filename*=iso-8859-1''\x69\x27\x27\x93\x93\x93\x93\xff\x61\x61\x61\x61\x61\x61\x61\x62\x63\x64\x65\x0a; foo",
+              {
+                    { "filename", "i''\302\223\302\223\302\223\302\223\303\277aaaaaaabcde" },
+                    { "invalid", "\302\223\302\223\302\223\302\223\303\277aaaaaaabcde" },
+                    { "foo", NULL },
+
+                },
+        }
 };
 static const int num_paramlisttests = G_N_ELEMENTS (paramlisttests);
 
@@ -1034,6 +1062,7 @@
 #define RFC5987_TEST_HEADER_FALLBACK "attachment; filename*=Unknown''t%FF%FF%FFst.txt; filename=\"test.txt\""
 #define RFC5987_TEST_HEADER_NO_TYPE  "filename=\"test.txt\""
 #define RFC5987_TEST_HEADER_NO_TYPE_2  "filename=\"test.txt\"; foo=bar"
+#define RFC5987_TEST_HEADER_EMPTY_FILENAME ";filename"
 
 static void
 do_content_disposition_tests (void)
@@ -1134,6 +1163,20 @@
         g_assert_cmpstr (parameter2, ==, "bar");
 	g_hash_table_destroy (params);
 
+        /* Empty filename */
+        soup_message_headers_clear (hdrs);
+        soup_message_headers_append (hdrs, "Content-Disposition",
+				     RFC5987_TEST_HEADER_EMPTY_FILENAME);
+	if (!soup_message_headers_get_content_disposition (hdrs,
+							   &disposition,
+							   &params)) {
+		soup_test_assert (FALSE, "empty filename decoding FAILED");
+		return;
+	}
+        g_free (disposition);
+        g_assert_false (g_hash_table_contains (params, "filename"));
+	g_hash_table_destroy (params);
+
 	soup_message_headers_unref (hdrs);
 
 	/* Ensure that soup-multipart always quotes filename */
diff -Nru libsoup3-3.2.2/tests/meson.build libsoup3-3.2.3/tests/meson.build
--- libsoup3-3.2.2/tests/meson.build	2025-07-12 14:43:24.000000000 +0100
+++ libsoup3-3.2.3/tests/meson.build	2025-07-12 14:43:24.000000000 +0100
@@ -95,7 +95,9 @@
   {'name': 'session'},
   {'name': 'server-auth'},
   {'name': 'server'},
-  {'name': 'sniffing'},
+  {'name': 'sniffing',
+    'depends': [test_resources],
+  },
   {'name': 'ssl',
    'dependencies': [gnutls_dep],
    'depends': mock_pkcs11_module,
diff -Nru libsoup3-3.2.2/tests/sniffing-test.c libsoup3-3.2.3/tests/sniffing-test.c
--- libsoup3-3.2.2/tests/sniffing-test.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/tests/sniffing-test.c	2025-07-12 14:43:24.000000000 +0100
@@ -342,6 +342,52 @@
 	g_uri_unref (uri);
 }
 
+static const gsize MARKUP_LENGTH = strlen ("<!--") + strlen ("-->");
+
+static void
+do_skip_whitespace_test (void)
+{
+        SoupContentSniffer *sniffer = soup_content_sniffer_new ();
+        SoupMessage *msg = soup_message_new (SOUP_METHOD_GET, "http://example.org");
+        const char *test_cases[] = {
+                "",
+                "<rdf:RDF",
+                "<rdf:RDFxmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"",
+                "<rdf:RDFxmlns=\"http://purl.org/rss/1.0/\"",
+        };
+
+        soup_message_headers_set_content_type (soup_message_get_response_headers (msg), "text/html", NULL);
+
+        for (guint i = 0; i < G_N_ELEMENTS (test_cases); i++) {
+                const char *trailing_data = test_cases[i];
+                gsize leading_zeros = 512 - MARKUP_LENGTH - strlen (trailing_data);
+                gsize testsize = MARKUP_LENGTH + leading_zeros + strlen (trailing_data);
+                guint8 *data = g_malloc0 (testsize);
+                guint8 *p = data;
+                char *content_type;
+                GBytes *buffer;
+
+                // Format of <!--[0x00 * $leading_zeros]-->$trailing_data
+                memcpy (p, "<!--", strlen ("<!--"));
+                p += strlen ("<!--");
+                p += leading_zeros;
+                memcpy (p, "-->", strlen ("-->"));
+                p += strlen ("-->");
+                if (strlen (trailing_data))
+                        memcpy (p, trailing_data, strlen (trailing_data));
+                // Purposefully not NUL terminated.                
+
+                buffer = g_bytes_new_take (g_steal_pointer (&data), testsize);
+                content_type = soup_content_sniffer_sniff (sniffer, msg, buffer, NULL);
+
+                g_free (content_type);
+                g_bytes_unref (buffer);
+        }
+
+        g_object_unref (msg);
+        g_object_unref (sniffer);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -517,6 +563,8 @@
 			      "/text_or_binary/home.gif",
 			      test_disabled);
 
+	g_test_add_func ("/sniffing/whitespace", do_skip_whitespace_test);
+
 	ret = g_test_run ();
 
 	g_uri_unref (base_uri);
diff -Nru libsoup3-3.2.2/tests/uri-parsing-test.c libsoup3-3.2.3/tests/uri-parsing-test.c
--- libsoup3-3.2.2/tests/uri-parsing-test.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/tests/uri-parsing-test.c	2024-11-25 00:39:07.000000000 +0000
@@ -131,6 +131,8 @@
         { "data:text/plain;base64,aGVsbG8=", "hello", "text/plain" },
         { "data:text/plain;base64,invalid=", "", "text/plain" },
         { "data:,", "", CONTENT_TYPE_DEFAULT },
+        { "data:.///", NULL, NULL },
+        { "data:/.//", NULL, NULL },
 };
 
 static void
diff -Nru libsoup3-3.2.2/tests/websocket-test.c libsoup3-3.2.3/tests/websocket-test.c
--- libsoup3-3.2.2/tests/websocket-test.c	2022-11-02 19:46:22.000000000 +0000
+++ libsoup3-3.2.3/tests/websocket-test.c	2024-11-25 00:39:07.000000000 +0000
@@ -1408,8 +1408,9 @@
 	GError *error = NULL;
 	InvalidEncodeLengthTest context = { test, NULL };
 	guint i;
+	guint error_id;
 
-	g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error);
+	error_id = g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error);
 	g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received);
 
 	/* We use 126(~) as payload length with 125 extended length */
@@ -1422,6 +1423,7 @@
 	WAIT_UNTIL (error != NULL || received != NULL);
 	g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR);
 	g_clear_error (&error);
+        g_signal_handler_disconnect (test->client, error_id);
 	g_assert_null (received);
 
 	g_thread_join (thread);
@@ -1439,8 +1441,9 @@
 	GError *error = NULL;
 	InvalidEncodeLengthTest context = { test, NULL };
 	guint i;
+	guint error_id;
 
-	g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error);
+	error_id = g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error);
 	g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received);
 
 	/* We use 127(\x7f) as payload length with 65535 extended length */
@@ -1453,6 +1456,7 @@
 	WAIT_UNTIL (error != NULL || received != NULL);
 	g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR);
 	g_clear_error (&error);
+        g_signal_handler_disconnect (test->client, error_id);
 	g_assert_null (received);
 
         g_thread_join (thread);
