Commit 1b6e96cb5ec for php

commit 1b6e96cb5ecc984e6f1d568ce578129a7f8359d9
Author: Daniel Scherzer <daniel.e.scherzer+phpf@gmail.com>
Date:   Tue Sep 15 21:54:41 2026 -0700

    ext/soap: redact more sensitive headers

diff --git a/NEWS b/NEWS
index 6852f98646d..3a663fa341d 100644
--- a/NEWS
+++ b/NEWS
@@ -95,6 +95,10 @@ PHP                                                                        NEWS
   . Fixed session data loss in the files save handler when writing the session
     file fails after it was already truncated. (Ilia Alshanetsky)

+- SOAP:
+  . Fixed bug GH-23686 (SoapClient only strips `Authorization: Basic` when a
+    WSDL imports from another host). (DanielEScherzer)
+
 - Standard:
   . Fixed chown() and lchown() failing to resolve user names in ZTS builds
     when getpwnam_r() needs a larger buffer. (Ilia Alshanetsky)
diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c
index 517f8a6afd2..a2065cb0a81 100644
--- a/ext/soap/php_sdl.c
+++ b/ext/soap/php_sdl.c
@@ -198,6 +198,75 @@ static int is_wsdl_element(xmlNodePtr node)
 	return 1;
 }

+// Which headers need to be redacted
+#define REDACT_NONE 0
+#define REDACT_AUTHORIZATION (1 << 0)
+#define REDACT_PROXY_AUTH (1 << 1)
+#define REDACT_COOKIE (1 << 2)
+
+static uint32_t header_needs_redaction(zend_string *header) {
+	uint32_t redaction = REDACT_NONE;
+	/* The various loops are so that if the first use of "authorization" is in
+	 * some other header, we still redact the actual "authorization" header.
+	 *
+	 * We check if the character before the start of the header is a '\n' but
+	 * not if the character before that was a `\r`; per RFC9112 while the
+	 * separator is officially both (CRLF) recipients "MAY" recognize just a
+	 * '\n' (LF) as a line terminator. A recipient "MUST" treat a bare '\r' (CR)
+	 * as invalid or replace it, so a '\r' on its own does not start a new
+	 * header.
+	 */
+	char *auth_start = strstr(ZSTR_VAL(header), "authorization:");
+	while (auth_start && *auth_start) {
+		if (auth_start == ZSTR_VAL(header) || *(auth_start - 1) == '\n') {
+			redaction |= REDACT_AUTHORIZATION;
+			break;
+		}
+		auth_start = strstr(auth_start + 1, "authorization:");
+	}
+
+	char *proxy_start = strstr(ZSTR_VAL(header), "proxy-authorization:");
+	while (proxy_start && *proxy_start) {
+		if (proxy_start == ZSTR_VAL(header) || *(proxy_start - 1) == '\n') {
+			redaction |= REDACT_PROXY_AUTH;
+			break;
+		}
+		proxy_start = strstr(proxy_start + 1, "proxy-authorization:");
+	}
+
+	char *cookie_start = strstr(ZSTR_VAL(header), "cookie:");
+	while (cookie_start && *cookie_start) {
+		if (cookie_start == ZSTR_VAL(header) || *(cookie_start - 1) == '\n') {
+			redaction |= REDACT_COOKIE;
+			break;
+		}
+		cookie_start = strstr(cookie_start + 1, "cookie:");
+	}
+	return redaction;
+}
+
+static bool redact_header(char *header_start, uint32_t to_redact) {
+	/* When redacting, we don't remove the REDACT_* flag from to_redact, because
+	 * a header might have been specified multiple times and we want to remove
+	 * all of those uses. */
+	if (to_redact & REDACT_AUTHORIZATION
+		&& strncmp(header_start, "authorization:", strlen("authorization:")) == 0
+	) {
+		return true;
+	}
+	if (to_redact & REDACT_PROXY_AUTH
+		&& strncmp(header_start, "proxy-authorization:", strlen("proxy-authorization:")) == 0
+	) {
+		return true;
+	}
+	if (to_redact & REDACT_COOKIE
+		&& strncmp(header_start, "cookie:", strlen("cookie:")) == 0
+	) {
+		return true;
+	}
+	return false;
+}
+
 void sdl_set_uri_credentials(sdlCtx *ctx, char *uri)
 {
 	char *s;
@@ -264,26 +333,107 @@ void sdl_set_uri_credentials(sdlCtx *ctx, char *uri)
 		return;
 	}

-	if ((header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL &&
-		Z_TYPE_P(header) == IS_STRING
-	) {
-		/* TODO: should support header as an array, but this code path is untested */
-		s = strstr(Z_STRVAL_P(header), "Authorization: Basic");
-		if (s && (s == Z_STRVAL_P(header) || *(s-1) == '\n' || *(s-1) == '\r')) {
-			char *rest = strstr(s, "\r\n");
-			if (rest) {
-				zval new_header;
+	header = php_stream_context_get_option(ctx->context, "http", "header");
+	if (header == NULL) {
+		return;
+	}

-				rest += 2;
-				ZVAL_NEW_STR(&new_header, zend_string_alloc(Z_STRLEN_P(header) - (rest - s), 0));
-				memcpy(Z_STRVAL(new_header), Z_STRVAL_P(header), s - Z_STRVAL_P(header));
-				memcpy(Z_STRVAL(new_header) + (s - Z_STRVAL_P(header)), rest, Z_STRLEN_P(header) - (rest - Z_STRVAL_P(header)) + 1);
-				ZVAL_COPY(&ctx->old_header, header);
-				php_stream_context_set_option(ctx->context, "http", "header", &new_header);
-				zval_ptr_dtor(&new_header);
+	zend_string *flat_headers;
+	if (Z_TYPE_P(header) == IS_STRING) {
+		flat_headers = zend_string_copy(Z_STR_P(header));
+	} else if (Z_TYPE_P(header) == IS_ARRAY) {
+		smart_str buf = {0};
+		ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(header), zval *value) {
+			if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value)) {
+				if (buf.s) {
+					smart_str_appendl(&buf, "\r\n", 2);
+				}
+				smart_str_append(&buf, Z_STR_P(value));
+			}
+		} ZEND_HASH_FOREACH_END();
+		flat_headers = smart_str_extract(&buf);
+	} else {
+		return;
+	}
+	zend_string *lc_headers = zend_string_tolower(flat_headers);
+	// Fast path: no redaction needed
+	uint32_t redaction = header_needs_redaction(lc_headers);
+	if (redaction == REDACT_NONE) {
+		zend_string_release(lc_headers);
+		zend_string_release(flat_headers);
+		return;
+	}
+	// At least one of the headers needs to be stripped. To avoid repeated
+	// copying and case comparisons, we go through the headers one at a time.
+	// Will be allocated on the first header that should be kept.
+	zend_string *redacted = NULL;
+	// Pointer to the current read location
+	char *read_ptr = ZSTR_VAL(flat_headers);
+	// Offset from the start of the read location
+	size_t read_offset = 0;
+	// Offset from the start of the write location
+	size_t write_offset = 0;
+
+	while (true) {
+		if (redact_header(ZSTR_VAL(lc_headers) + read_offset, redaction)) {
+			/* Current header should be skipped
+			 * As with header_needs_redaction() we treat a '\n' as the end
+			 * of the header even if not preceded by '\r'. */
+			char *header_end = memchr(read_ptr, '\n', ZSTR_LEN(flat_headers) - read_offset);
+			if (header_end) {
+				// Skip the current header and the \n
+				size_t header_length = (header_end - read_ptr) + 1;
+				read_offset += header_length;
+				read_ptr += header_length;
+			} else {
+				// This is the end
+				break;
+			}
+		} else {
+			// Current header (from `read_offset`) should be kept
+			if (redacted == NULL) {
+				// This will over allocate the redacted header string but
+				// that isn't a big deal, the headers should be fairly short
+				redacted = zend_string_alloc(ZSTR_LEN(flat_headers), false);
+			}
+			char *header_end = memchr(read_ptr, '\n', ZSTR_LEN(flat_headers) - read_offset);
+			if (header_end) {
+				// Copy the current header and the \n
+				size_t header_length = (header_end - read_ptr) + 1;
+				memcpy(ZSTR_VAL(redacted) + write_offset, read_ptr, header_length);
+				// Update offsets
+				read_offset += header_length;
+				read_ptr += header_length;
+				write_offset += header_length;
+			} else {
+				// Copy until the end of the headers
+				size_t header_length = ZSTR_LEN(lc_headers) - read_offset;
+				memcpy(ZSTR_VAL(redacted) + write_offset, read_ptr, header_length);
+				write_offset += header_length;
+				break;
 			}
 		}
 	}
+	zend_string_release(lc_headers);
+	zend_string_release(flat_headers);
+
+	ZVAL_COPY(&ctx->old_header, header);
+	zval new_header;
+	if (redacted == NULL) {
+		// All headers were redacted
+		ZVAL_EMPTY_STRING(&new_header);
+	} else {
+		// Only some headers were redacted
+		// The zval_ptr_dtor call will ensure that the redacted headers
+		// are freed;
+		// Make sure that we don't read garbage
+		ZSTR_VAL(redacted)[write_offset] = '\0';
+		ZSTR_LEN(redacted) = write_offset;
+
+		ZVAL_STR(&new_header, redacted);
+	}
+	php_stream_context_set_option(ctx->context, "http", "header", &new_header);
+	zval_ptr_dtor(&new_header);
 }

 void sdl_restore_uri_credentials(sdlCtx *ctx)
diff --git a/ext/soap/tests/gh23686/header-just-lf-separator.phpt b/ext/soap/tests/gh23686/header-just-lf-separator.phpt
index 11a3a688cdf..fc743aaed77 100644
--- a/ext/soap/tests/gh23686/header-just-lf-separator.phpt
+++ b/ext/soap/tests/gh23686/header-just-lf-separator.phpt
@@ -23,7 +23,7 @@

 // Headers reach sdl_set_uri_credentials() as a string
 $context = stream_context_create([
-	'http' => ['header' => "Authorization: Basic\nFoo: bar"],
+	'http' => ['header' => "Authorization: Basic\nFoo: bar\nProxy-Authorization: Something\nBaz: Qux\nCookie: Tasty"],
 ]);

 echo "Headers are in a string:\n";
@@ -35,7 +35,7 @@
 	'http' => [
 		// having a protocol here means that SOAP won't add it itself
 		'protocol_version' => 1.1,
-		'header' => "Authorization: Basic\nFoo: bar"
+		'header' => "Authorization: Basic\nFoo: bar\nProxy-Authorization: Something\nBaz: Qux\nCookie: Tasty"
 	],
 ]);

@@ -50,44 +50,48 @@
   'Host' => 'localhost:%d',
   'Connection' => 'close',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 Headers are in an array:
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Basic',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Basic',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Basic',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Basic',
   'Foo' => 'bar',
+  'Baz' => 'Qux',
 )"
diff --git a/ext/soap/tests/gh23686/import-authorization-basic-lowercase.phpt b/ext/soap/tests/gh23686/import-authorization-basic-lowercase.phpt
index fa27f1c87cf..7d0c694dc63 100644
--- a/ext/soap/tests/gh23686/import-authorization-basic-lowercase.phpt
+++ b/ext/soap/tests/gh23686/import-authorization-basic-lowercase.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'authorization' => 'Basic foo bar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'authorization' => 'Basic foo bar',
 )"
diff --git a/ext/soap/tests/gh23686/import-authorization-bearer.phpt b/ext/soap/tests/gh23686/import-authorization-bearer.phpt
index 8eb87a52653..e8445e8b326 100644
--- a/ext/soap/tests/gh23686/import-authorization-bearer.phpt
+++ b/ext/soap/tests/gh23686/import-authorization-bearer.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Bearer secret-token',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Bearer secret-token',
 )"
diff --git a/ext/soap/tests/gh23686/import-authorization-digest.phpt b/ext/soap/tests/gh23686/import-authorization-digest.phpt
index d74058bb4cf..ff3374b0c84 100644
--- a/ext/soap/tests/gh23686/import-authorization-digest.phpt
+++ b/ext/soap/tests/gh23686/import-authorization-digest.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Digest secret-token',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Digest secret-token',
 )"
diff --git a/ext/soap/tests/gh23686/import-cookies.phpt b/ext/soap/tests/gh23686/import-cookies.phpt
index d7b47daa556..7871ca0874f 100644
--- a/ext/soap/tests/gh23686/import-cookies.phpt
+++ b/ext/soap/tests/gh23686/import-cookies.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Cookie' => 'foo=bar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Cookie' => 'foo=bar',
 )"
diff --git a/ext/soap/tests/gh23686/import-proxy-authorization.phpt b/ext/soap/tests/gh23686/import-proxy-authorization.phpt
index e0b0bb0a457..f4356f31284 100644
--- a/ext/soap/tests/gh23686/import-proxy-authorization.phpt
+++ b/ext/soap/tests/gh23686/import-proxy-authorization.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Proxy-Authorization' => 'FooBar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Proxy-Authorization' => 'FooBar',
 )"
diff --git a/ext/soap/tests/gh23686/import-repeated-authorization.phpt b/ext/soap/tests/gh23686/import-repeated-authorization.phpt
index 3b7d5c16726..7ec369b7b92 100644
--- a/ext/soap/tests/gh23686/import-repeated-authorization.phpt
+++ b/ext/soap/tests/gh23686/import-repeated-authorization.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated authorization headers (which doesn't work) (`<import>`)
+GH-23686: Regression tests for repeated authorization headers (which previously didn't work) (`<import>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,13 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
diff --git a/ext/soap/tests/gh23686/import-repeated-cookies.phpt b/ext/soap/tests/gh23686/import-repeated-cookies.phpt
index ebe67c075f3..b6c4f2e1d1d 100644
--- a/ext/soap/tests/gh23686/import-repeated-cookies.phpt
+++ b/ext/soap/tests/gh23686/import-repeated-cookies.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated cookies headers (which doesn't work) (`<import>`)
+GH-23686: Regression tests for repeated cookies headers (which previously didn't work) (`<import>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,13 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
diff --git a/ext/soap/tests/gh23686/import-repeated-proxy-auth.phpt b/ext/soap/tests/gh23686/import-repeated-proxy-auth.phpt
index edeb149e4ca..4628fcb7fe0 100644
--- a/ext/soap/tests/gh23686/import-repeated-proxy-auth.phpt
+++ b/ext/soap/tests/gh23686/import-repeated-proxy-auth.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated proxy authorization headers (which doesn't work) (`<import>`)
+GH-23686: Regression tests for repeated proxy authorization headers (which previously didn't work) (`<import>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,13 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-SoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost:%d/index.php' : failed to load external entity "http://localhost:%d/index.php"
-
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
diff --git a/ext/soap/tests/gh23686/redact-array-headers.phpt b/ext/soap/tests/gh23686/redact-array-headers.phpt
new file mode 100644
index 00000000000..ca27bf278a6
--- /dev/null
+++ b/ext/soap/tests/gh23686/redact-array-headers.phpt
@@ -0,0 +1,74 @@
+--TEST--
+GH-23686: Verify that a header is redacted when it reaches sdl_set_uri_credentials() as an array
+--DESCRIPTION--
+Most of the time soap will add its own headers and in the process convert user headers
+from an array to a string, check for the case when that does not happen
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=0
+--CLEAN--
+<?php
+@unlink(__DIR__ . "/redact-array-headers.txt");
+?>
+--SKIPIF--
+<?php
+if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
+	echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
+}
+?>
+--FILE--
+<?php
+
+include __DIR__ . "/check_headers.inc";
+
+const LOGS_PATH = __DIR__ . "/redact-array-headers.txt";
+
+$context = stream_context_create([
+	'http' => [
+		// having a protocol here means that SOAP won't add it itself
+		'protocol_version' => 1.1,
+		'header' => [
+			"X-Authorization: This should be kept",
+			"Authorization: This should be removed",
+			"X-Proxy-Authorization: This should also be kept",
+			"Proxy-Authorization: This should also be removed",
+			"X-Cookie: Last one to keep",
+			"Cookie: Last one to remove",
+		]
+	],
+]);
+
+check_headers_for_import(LOGS_PATH, $context);
+check_headers_for_schema(LOGS_PATH, $context);
+
+?>
+--EXPECTF--
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
diff --git a/ext/soap/tests/gh23686/redact-not-first.phpt b/ext/soap/tests/gh23686/redact-not-first.phpt
new file mode 100644
index 00000000000..f85b80f47b5
--- /dev/null
+++ b/ext/soap/tests/gh23686/redact-not-first.phpt
@@ -0,0 +1,67 @@
+--TEST--
+GH-23686: Verify that a header is redacted when it isn't the first use of the name
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=0
+--CLEAN--
+<?php
+@unlink(__DIR__ . "/redact-not-first.txt");
+?>
+--SKIPIF--
+<?php
+if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
+	echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
+}
+?>
+--FILE--
+<?php
+
+include __DIR__ . "/check_headers.inc";
+
+const LOGS_PATH = __DIR__ . "/redact-not-first.txt";
+
+$context = stream_context_create([
+	'http' => ['header' => [
+		"X-Authorization: This should be kept",
+		"Authorization: This should be removed",
+		"X-Proxy-Authorization: This should also be kept",
+		"Proxy-Authorization: This should also be removed",
+		"X-Cookie: Last one to keep",
+		"Cookie: Last one to remove",
+	]],
+]);
+
+check_headers_for_import(LOGS_PATH, $context);
+check_headers_for_schema(LOGS_PATH, $context);
+
+?>
+--EXPECTF--
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+  'X-Authorization' => 'This should be kept',
+  'X-Proxy-Authorization' => 'This should also be kept',
+  'X-Cookie' => 'Last one to keep',
+)"
diff --git a/ext/soap/tests/gh23686/schema-authorization-basic-lowercase.phpt b/ext/soap/tests/gh23686/schema-authorization-basic-lowercase.phpt
index dedee8b85c4..8a55c870d7a 100644
--- a/ext/soap/tests/gh23686/schema-authorization-basic-lowercase.phpt
+++ b/ext/soap/tests/gh23686/schema-authorization-basic-lowercase.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'authorization' => 'Basic foo bar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'authorization' => 'Basic foo bar',
 )"
diff --git a/ext/soap/tests/gh23686/schema-authorization-bearer.phpt b/ext/soap/tests/gh23686/schema-authorization-bearer.phpt
index be786c8f6b1..8b575c74edd 100644
--- a/ext/soap/tests/gh23686/schema-authorization-bearer.phpt
+++ b/ext/soap/tests/gh23686/schema-authorization-bearer.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Bearer secret-token',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Bearer secret-token',
 )"
diff --git a/ext/soap/tests/gh23686/schema-authorization-digest.phpt b/ext/soap/tests/gh23686/schema-authorization-digest.phpt
index ad48118d6c0..a218e694d77 100644
--- a/ext/soap/tests/gh23686/schema-authorization-digest.phpt
+++ b/ext/soap/tests/gh23686/schema-authorization-digest.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Digest secret-token',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Authorization' => 'Digest secret-token',
 )"
diff --git a/ext/soap/tests/gh23686/schema-cookies.phpt b/ext/soap/tests/gh23686/schema-cookies.phpt
index ff33d8ace5d..57c5984eee8 100644
--- a/ext/soap/tests/gh23686/schema-cookies.phpt
+++ b/ext/soap/tests/gh23686/schema-cookies.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Cookie' => 'foo=bar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Cookie' => 'foo=bar',
 )"
diff --git a/ext/soap/tests/gh23686/schema-proxy-authorization.phpt b/ext/soap/tests/gh23686/schema-proxy-authorization.phpt
index 2f06bc9e735..51920558c9b 100644
--- a/ext/soap/tests/gh23686/schema-proxy-authorization.phpt
+++ b/ext/soap/tests/gh23686/schema-proxy-authorization.phpt
@@ -32,10 +32,8 @@
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Proxy-Authorization' => 'FooBar',
 )"
 string(%d) "array (
   'Host' => 'localhost:%d',
   'Connection' => 'close',
-  'Proxy-Authorization' => 'FooBar',
 )"
diff --git a/ext/soap/tests/gh23686/schema-repeated-authorization.phpt b/ext/soap/tests/gh23686/schema-repeated-authorization.phpt
index e99c7c9c7d9..066e1541b0b 100644
--- a/ext/soap/tests/gh23686/schema-repeated-authorization.phpt
+++ b/ext/soap/tests/gh23686/schema-repeated-authorization.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated authorization headers (which doesn't work) (`<schema>`)
+GH-23686: Regression tests for repeated authorization headers (which previously didn't work) (`<schema>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,11 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
diff --git a/ext/soap/tests/gh23686/schema-repeated-cookies.phpt b/ext/soap/tests/gh23686/schema-repeated-cookies.phpt
index 76cc2b95a6d..5eaa8ef9465 100644
--- a/ext/soap/tests/gh23686/schema-repeated-cookies.phpt
+++ b/ext/soap/tests/gh23686/schema-repeated-cookies.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated cookies headers (which doesn't work) (`<schema>`)
+GH-23686: Regression tests for repeated cookies headers (which previously didn't work) (`<schema>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,11 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
diff --git a/ext/soap/tests/gh23686/schema-repeated-proxy-auth.phpt b/ext/soap/tests/gh23686/schema-repeated-proxy-auth.phpt
index 9e6b2e6768b..e23759b03a9 100644
--- a/ext/soap/tests/gh23686/schema-repeated-proxy-auth.phpt
+++ b/ext/soap/tests/gh23686/schema-repeated-proxy-auth.phpt
@@ -1,5 +1,5 @@
 --TEST--
-GH-23686: Regression tests for repeated proxy authorization headers (which doesn't work) (`<schema>`)
+GH-23686: Regression tests for repeated proxy authorization headers (which previously didn't work) (`<schema>`)
 --EXTENSIONS--
 soap
 --INI--
@@ -32,11 +32,11 @@

 ?>
 --EXPECTF--
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-SoapFault: SOAP-ERROR: Parsing Schema: can't import schema from 'http://localhost:%d/index.php'
-bool(false)
-Server exited with non-zero status: 1
-
-Warning: file_get_contents(): Failed to open stream: No such file or directory in %s on line %d
-Server output:
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"
+string(%d) "array (
+  'Host' => 'localhost:%d',
+  'Connection' => 'close',
+)"