Commit de39a14cc74 for php.net

commit de39a14cc740ce11e271d6f0569718b4c9dc06ac
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Tue Jun 16 18:17:09 2026 -0400

    Fix SOAP client failing to follow a scheme-less redirect Location

    The HTTP redirect handler inherited scheme, host and port from the newly
    parsed Location URI itself instead of the previous request URI, so a
    scheme-less (relative or absolute-path) Location left the host NULL and
    the retry aborted with "Unable to parse URL". Read the inherited
    components and the path base from the request URI, restoring the
    behaviour the URI-parser refactor changed.

    Closes GH-22341

diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c
index 944b033363c..c7f3732f298 100644
--- a/ext/soap/php_http.c
+++ b/ext/soap/php_http.c
@@ -1162,12 +1162,12 @@ int make_http_soap_request(
 				zend_string_release_ex(http_headers, 0);
 				zend_string_release_ex(http_body, 0);
 				if (new_uri->scheme == NULL && new_uri->path != NULL) {
-					new_uri->scheme = new_uri->scheme ? zend_string_copy(new_uri->scheme) : NULL;
-					new_uri->host = new_uri->host ? zend_string_copy(new_uri->host) : NULL;
-					new_uri->port = new_uri->port;
+					new_uri->scheme = uri->scheme ? zend_string_copy(uri->scheme) : NULL;
+					new_uri->host = uri->host ? zend_string_copy(uri->host) : NULL;
+					new_uri->port = uri->port;
 					if (new_uri->path && ZSTR_VAL(new_uri->path)[0] != '/') {
-						if (new_uri->path) {
-							char *t = ZSTR_VAL(new_uri->path);
+						if (uri->path) {
+							char *t = ZSTR_VAL(uri->path);
 							char *p = strrchr(t, '/');
 							if (p) {
 								zend_string *s = zend_string_alloc((p - t) + ZSTR_LEN(new_uri->path) + 2, 0);
diff --git a/ext/soap/tests/bugs/relative_redirect.phpt b/ext/soap/tests/bugs/relative_redirect.phpt
new file mode 100644
index 00000000000..774e7cbd98d
--- /dev/null
+++ b/ext/soap/tests/bugs/relative_redirect.phpt
@@ -0,0 +1,49 @@
+--TEST--
+SOAP client follows a redirect with a scheme-less (relative) Location
+--EXTENSIONS--
+soap
+--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__ . "/../../../../sapi/cli/tests/php_cli_server.inc";
+
+$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
+if (php_ini_loaded_file()) {
+    $args[] = "-c";
+    $args[] = php_ini_loaded_file();
+}
+
+$code = <<<'PHP'
+if ($_SERVER["REQUEST_URI"] === "/redirected") {
+    header("Content-Type: text/xml; charset=utf-8");
+    echo '<?xml version="1.0" encoding="UTF-8"?>',
+        '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">',
+        '<SOAP-ENV:Body><fooResponse><result>ok</result></fooResponse></SOAP-ENV:Body>',
+        '</SOAP-ENV:Envelope>';
+} else {
+    http_response_code(302);
+    header("Location: /redirected");
+}
+PHP;
+
+php_cli_server_start($code, null, $args);
+
+$client = new SoapClient(null, [
+    'location' => 'http://' . PHP_CLI_SERVER_ADDRESS . '/start',
+    'uri' => 'test-uri',
+]);
+
+try {
+    $client->__soapCall("foo", []);
+    echo "redirect followed\n";
+} catch (SoapFault $e) {
+    echo "SoapFault: " . $e->getMessage() . "\n";
+}
+?>
+--EXPECT--
+redirect followed
diff --git a/ext/soap/tests/bugs/relative_redirect_path.phpt b/ext/soap/tests/bugs/relative_redirect_path.phpt
new file mode 100644
index 00000000000..09d4c857cc9
--- /dev/null
+++ b/ext/soap/tests/bugs/relative_redirect_path.phpt
@@ -0,0 +1,49 @@
+--TEST--
+SOAP client follows a redirect with a relative Location resolved against the request path
+--EXTENSIONS--
+soap
+--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__ . "/../../../../sapi/cli/tests/php_cli_server.inc";
+
+$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
+if (php_ini_loaded_file()) {
+    $args[] = "-c";
+    $args[] = php_ini_loaded_file();
+}
+
+$code = <<<'PHP'
+if ($_SERVER["REQUEST_URI"] === "/svc/redirected") {
+    header("Content-Type: text/xml; charset=utf-8");
+    echo '<?xml version="1.0" encoding="UTF-8"?>',
+        '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">',
+        '<SOAP-ENV:Body><fooResponse><result>ok</result></fooResponse></SOAP-ENV:Body>',
+        '</SOAP-ENV:Envelope>';
+} else {
+    http_response_code(302);
+    header("Location: redirected");
+}
+PHP;
+
+php_cli_server_start($code, null, $args);
+
+$client = new SoapClient(null, [
+    'location' => 'http://' . PHP_CLI_SERVER_ADDRESS . '/svc/start',
+    'uri' => 'test-uri',
+]);
+
+try {
+    $client->__soapCall("foo", []);
+    echo "redirect followed\n";
+} catch (SoapFault $e) {
+    echo "SoapFault: " . $e->getMessage() . "\n";
+}
+?>
+--EXPECT--
+redirect followed