Skip to content

PHP 8.5 Soap server requires the raw input to be passed to $server->handle #22285

Closed

Description

@jahyvari

Description

The following code:

<?php
$server->handle();

Is working with PHP 8.4.

But with PHP 8.5 I had to change it to:

<?php
$server->handle(file_get_contents("php://input"));

Without it, the server is not handling the incoming requests properly. It seems to pick just the first method from the WSDL.

Could it be some kind of rewind issue, since the code is reading the php input before the handle function is called, to do custom Schema validation.

PHP Version

PHP 8.5.7 (cli) (built: Jun  2 2026 20:59:56) (NTS gcc x86_64)
Copyright (c) The PHP Group
Built by Remi's RPM repository <https://rpms.remirepo.net/> #StandWithUkraine
Zend Engine v4.5.7, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.7, Copyright (c), by Zend Technologies

Operating System

No response

Activity

  1. jahyvari commented on Jun 12, 2026

    @jahyvari
    Author

    This seems to be a similar issue: #22161

  2. LamentXU123 commented on Jun 12, 2026

    @LamentXU123
    Member

    Hi. Thank you for reporting. As people have mentioned in issue 22161, can you produce a small reproducible script for us to verify the bug? i.e. an example server script as well as the corresponding request package

  3. jahyvari commented on Jun 12, 2026

    @jahyvari
    Author

    Dockerfile

    FROM php:8.5-apache
    
    WORKDIR /var/www/html
    
    COPY hello.wsdl .
    COPY server.php .
    COPY client.php .
    
    RUN apt-get update && apt-get install -y libxml2-dev && docker-php-ext-install soap 
    
    EXPOSE 80
    
    CMD ["apache2-foreground"]
    

    hello.wsdl

    <?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>
    

    server.php

    <?php
    class TestWS {
        public function hello($params) {
            return ['message' => 'Hello ' . $params->name];
        }
        public function goodbye($params) {
            return ['message' => 'Goodbye ' . $params->name];
        }
    }
    
    $server = new SoapServer('hello.wsdl', [
        'cache_wsdl'   => WSDL_CACHE_NONE,
        'encoding'     => 'UTF-8',
        'soap_version' => SOAP_1_1
    ]);
    $server->setClass('TestWS');
    //$server->handle(file_get_contents('php://input'));
    $server->handle();
    

    client.php

    <?php
    $client = new SoapClient('hello.wsdl', [
        'cache_wsdl'   => WSDL_CACHE_NONE,
        'encoding'     => 'UTF-8',
        'soap_version' => SOAP_1_1,
        'trace'        => 1,
        'exceptions'   => true,
    ]);
    
    function printSoapDebug(SoapClient $client, string $method): void {
        echo "\n" . str_repeat("=", 60) . "\n";
        echo "METHOD: $method\n";
        echo str_repeat("=", 60) . "\n";
    
        echo "\n-- Request Headers --\n";
        echo $client->__getLastRequestHeaders();
    
        echo "\n-- Request Body --\n";
        echo $client->__getLastRequest();
    
        echo "\n-- Response Headers --\n";
        echo $client->__getLastResponseHeaders();
    
        echo "\n-- Response Body --\n";
        echo $client->__getLastResponse();
        echo "\n";
    }
    
    echo "-- Calling goodbye --\n";
    $result = $client->goodbye(['name' => 'World']);
    echo "Result: " . var_export($result, true) . "\n";
    printSoapDebug($client, 'goodbye');
    
  4. LamentXU123 commented on Jun 12, 2026

    @LamentXU123
    Member

    Thank you! This is reproduced on my machine. cc @devnexen as the code owner.

  5. ndossche commented on Jun 12, 2026

    @ndossche
    Member

    Since 8.5, ext/soap looks at the soapaction header to determine the action; unless it doesn't correspond to anything or is missing: then it falls back to the old (non-compliant & broken) system.
    In your example your soapactions are both empty. Why is that?

  6. jahyvari commented on Jun 12, 2026

    @jahyvari
    Author

    @ndossche So that the example is corresponding to the original WSDL file where I spotted this behaviour change between 8.4 vs 8.5.

    I did not see that the change is reported in here: https://www.php.net/migration85.incompatible.php

    Hence the bug report.

  7. ndossche commented on Jun 12, 2026

    @ndossche
    Member

    @ndossche So that the example is corresponding to the original WSDL file where I spotted this behaviour change between 8.4 vs 8.5.

    But that doesn't answer the question why the soap action is empty.
    It's possible to provide BC by making an exception for empty soap actions, that should work for broken WSDLs.

    I did not see that the change is reported in here: https://www.php.net/migration85.incompatible.php

    Because it shouldn't be an incompatible change, if the WSDL file is right.

  8. jahyvari commented on Jun 12, 2026

    @jahyvari
    Author

    @ndossche Over the years I have seen WSDL files where the soapAction is non-empty, and some files where it is an empty string (like this one), but still working in the SoapServer context.

    So it might be a worth to mention in other changes or backward incompatible changes, that non-compliant WSDL files might not work anymore as expected.

    But to understand the new logic, is it so that:

    • If the $request parameter for SoapServer::handle is empty/null, it tries to find the soap action header
    • But if it is not empty, it uses the passed request and behaves like pre PHP 8.5 even with the non-compliant WSDL files
  9. ndossche commented on Jun 12, 2026

    @ndossche
    Member

    Over the years I have seen WSDL files where the soapAction is non-empty, and some files where it is an empty string (like this one), but still working in the SoapServer context.

    In that case it might be best to go with the BC-preserving way.

    Here's a patch that would ignore empty soapaction headers, which should be non-sensical anyway.
    That would fix your issue:

    diff --git a/ext/soap/soap.c b/ext/soap/soap.c
    index b7ed4492987..2f51b6f61de 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);
     			    }
     			}
    

    cc @devnexen Please take care of this patch.

    But to understand the new logic, is it so that: (snip...)

    Your understanding is right.

  10. self-assigned this
    on Jun 14, 2026
  11. devnexen commented on Jun 14, 2026

    @devnexen
    Member

    Over the years I have seen WSDL files where the soapAction is non-empty, and some files where it is an empty string (like this one), but still working in the SoapServer context.

    In that case it might be best to go with the BC-preserving way.

    Here's a patch that would ignore empty soapaction headers, which should be non-sensical anyway. That would fix your issue:

    diff --git a/ext/soap/soap.c b/ext/soap/soap.c
    index b7ed4492987..2f51b6f61de 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);
      	    }
      	}
      

    cc @devnexen Please take care of this patch.

    Almost fix it you ll see.

    But to understand the new logic, is it so that: (snip...)

    Your understanding is right.

  12. added a commit that references this issue on Jun 15, 2026
    a9987fa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions