Commit a9987fa80e2 for php.net

commit a9987fa80e20249d57ba4d951a72c5b00056142f
Author: David Carlier <devnexen@gmail.com>
Date:   Sun Jun 14 13:44:18 2026 +0100

    ext/soap: ignore empty SOAPAction header during server dispatch.

    Fix #22285

    Since 8.5 SoapServer::handle() uses the HTTP SOAPAction header to select
    the operation, falling back to the request body otherwise. An empty
    header made every request match the first WSDL operation. Skip empty
    SOAPAction headers so dispatch falls back to body inspection.

    close GH-22304

diff --git a/NEWS b/NEWS
index 235699b12be..bd575259600 100644
--- a/NEWS
+++ b/NEWS
@@ -44,6 +44,8 @@ PHP                                                                        NEWS
 - SOAP:
   . Fixed bug GH-22218 (SoapServer::handle() crash on $_SERVER not being
     an array). (David Carlier / Rex-Reynolds)
+  . Fixed bug GH-22285 (Soap server requires the raw input to be passed
+    to $server->handle). (David Carlier / ndossche)

 - Sqlite:
   . Fix error checks for column retrieval. (ndossche)
diff --git a/ext/soap/soap.c b/ext/soap/soap.c
index b7ed4492987..2bb49ee4c8f 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -1394,7 +1394,7 @@ PHP_METHOD(SoapServer, handle)
 					}
 				}

-			    if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING) {
+			    if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING && Z_STRLEN_P(soap_action_z) > 0) {
 				    soap_action = Z_STRVAL_P(soap_action_z);
 			    }
 			}
@@ -3178,6 +3178,10 @@ static sdlFunctionPtr find_function_using_soap_action(const sdl *sdl, const char
 		soap_action_length -= 2;
 	}

+	if (UNEXPECTED(soap_action_length == 0)) {
+		return NULL;
+	}
+
 	/* TODO: This may depend on a particular target namespace, in which case this won't find a match when multiple different
 	 *       target namespaces are used until #45282 is resolved. */
 	sdlFunctionPtr function;
diff --git a/ext/soap/tests/bugs/gh22285.phpt b/ext/soap/tests/bugs/gh22285.phpt
new file mode 100644
index 00000000000..8c7e0933588
--- /dev/null
+++ b/ext/soap/tests/bugs/gh22285.phpt
@@ -0,0 +1,45 @@
+--TEST--
+GH-22285 (SoapServer dispatches to the first function when the SOAPAction header is empty)
+--CREDITS--
+Jarkko Hyvärinen
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=0
+--SKIPIF--
+<?php
+    if (php_sapi_name()=='cli') echo 'skip';
+?>
+--POST--
+<SOAP-ENV:Envelope
+    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+    xmlns:ns1="urn:TestService">
+    <SOAP-ENV:Body>
+        <ns1:goodbyeIn>
+            <name>World</name>
+        </ns1:goodbyeIn>
+    </SOAP-ENV:Body>
+</SOAP-ENV:Envelope>
+--FILE--
+<?php
+class TestWS {
+    public function hello($params) {
+        return ['message' => 'Hello ' . $params->name];
+    }
+    public function goodbye($params) {
+        return ['message' => 'Goodbye ' . $params->name];
+    }
+}
+
+$server = new SoapServer(__DIR__ . '/gh22285.wsdl', [
+    'cache_wsdl'   => WSDL_CACHE_NONE,
+    'encoding'     => 'UTF-8',
+    'soap_version' => SOAP_1_1,
+]);
+$server->setClass('TestWS');
+$_SERVER['HTTP_SOAPACTION'] = '""';
+$server->handle();
+?>
+--EXPECTF--
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:TestService"><SOAP-ENV:Body><ns1:goodbyeOut><message>Goodbye World</message></ns1:goodbyeOut></SOAP-ENV:Body></SOAP-ENV:Envelope>
diff --git a/ext/soap/tests/bugs/gh22285.wsdl b/ext/soap/tests/bugs/gh22285.wsdl
new file mode 100644
index 00000000000..66b9e674907
--- /dev/null
+++ b/ext/soap/tests/bugs/gh22285.wsdl
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wsdl:definitions
+    name="TestService"
+    targetNamespace="urn:TestService"
+    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:tns="urn:TestService"
+    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
+
+    <wsdl:types>
+        <xsd:schema targetNamespace="urn:TestService">
+            <xsd:element name="helloIn">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="name" type="xsd:string" />
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="helloOut">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="message" type="xsd:string" />
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="goodbyeIn">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="name" type="xsd:string" />
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="goodbyeOut">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="message" type="xsd:string" />
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+        </xsd:schema>
+    </wsdl:types>
+
+    <wsdl:message name="helloRequest">
+        <wsdl:part element="tns:helloIn" name="helloIn" />
+    </wsdl:message>
+    <wsdl:message name="helloResponse">
+        <wsdl:part element="tns:helloOut" name="helloOut" />
+    </wsdl:message>
+    <wsdl:message name="goodbyeRequest">
+        <wsdl:part element="tns:goodbyeIn" name="goodbyeIn" />
+    </wsdl:message>
+    <wsdl:message name="goodbyeResponse">
+        <wsdl:part element="tns:goodbyeOut" name="goodbyeOut" />
+    </wsdl:message>
+
+    <wsdl:portType name="TestServicePortType">
+        <wsdl:operation name="hello">
+            <wsdl:input  message="tns:helloRequest"   name="helloRequest" />
+            <wsdl:output message="tns:helloResponse"  name="helloResponse" />
+        </wsdl:operation>
+        <wsdl:operation name="goodbye">
+            <wsdl:input  message="tns:goodbyeRequest"  name="goodbyeRequest" />
+            <wsdl:output message="tns:goodbyeResponse" name="goodbyeResponse" />
+        </wsdl:operation>
+    </wsdl:portType>
+
+    <wsdl:binding name="TestServiceBinding" type="tns:TestServicePortType">
+        <wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
+        <wsdl:operation name="hello">
+            <wsdlsoap:operation soapAction="" />
+            <wsdl:input name="helloRequest">
+                <wsdlsoap:body use="literal" />
+            </wsdl:input>
+            <wsdl:output name="helloResponse">
+                <wsdlsoap:body use="literal" />
+            </wsdl:output>
+        </wsdl:operation>
+        <wsdl:operation name="goodbye">
+            <wsdlsoap:operation soapAction="" />
+            <wsdl:input name="goodbyeRequest">
+                <wsdlsoap:body use="literal" />
+            </wsdl:input>
+            <wsdl:output name="goodbyeResponse">
+                <wsdlsoap:body use="literal" />
+            </wsdl:output>
+        </wsdl:operation>
+    </wsdl:binding>
+
+    <wsdl:service name="TestService">
+        <wsdl:port name="TestServicePort" binding="tns:TestServiceBinding">
+            <wsdlsoap:address location="http://localhost/server.php" />
+        </wsdl:port>
+    </wsdl:service>
+
+</wsdl:definitions>