Skip to content

ext/soap: Fix SOAP classmap validation for integer keys#22884

Open
LamentXU123 wants to merge 3 commits into
php:masterfrom
LamentXU123:soap-todo
Open

ext/soap: Fix SOAP classmap validation for integer keys#22884
LamentXU123 wants to merge 3 commits into
php:masterfrom
LamentXU123:soap-todo

Conversation

@LamentXU123

Copy link
Copy Markdown
Member

Previously, SOAP only rejected packed arrays, which still allowed sparse integer-keyed arrays and mixed string integer-keyed arrays to be accepted.

Since classmap is expected to be an associative mapping, arrays containing integer keys are now rejected consistently.

@devnexen

Copy link
Copy Markdown
Member

could you add the following test please ?

--TEST--
SoapClient must not read a packed "classmap" array as a hash
--EXTENSIONS--
soap
--FILE--
<?php

class LocalClient extends SoapClient
{
    public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false, ?string $uriParserClass = null): ?string
    {
        echo $request, PHP_EOL;

        return <<<XML
        <?xml version="1.0" encoding="UTF-8"?>
        <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
          <SOAP-ENV:Body><ns1:doItResponse xmlns:ns1="urn:test"/></SOAP-ENV:Body>
        </SOAP-ENV:Envelope>
        XML;
    }
}

$client = new LocalClient(null, [
    'location' => 'http://example.com/',
    'uri' => 'urn:test',
    'classmap' => ['SomeType' => 'stdClass'],
]);

$property = new ReflectionProperty(SoapClient::class, '_classmap');
$property->setValue($client, ['stdClass', 'stdClass']);

$client->__soapCall('doIt', [new stdClass()]);

echo 'done', PHP_EOL;

?>
--EXPECTF--
%A
done

@LamentXU123
LamentXU123 force-pushed the soap-todo branch 2 times, most recently from 732faef to 751d593 Compare July 25, 2026 12:08
@LamentXU123

Copy link
Copy Markdown
Member Author

This is indeed a bug when SoapClient::_classmap can be reflected as a packed array :)

@devnexen

Copy link
Copy Markdown
Member

I would like to see the following in classmap_invalid_keys.phpt test :


    'sparse numeric' => [100 => 'stdClass'],
    'numeric string' => ['1' => 'stdClass'],

those new tests

--TEST--
SoapClient and SoapServer report an invalid "classmap" option differently
--EXTENSIONS--
soap
--FILE--
<?php

$classmap = ['type' => 'stdClass', 1 => 'stdClass'];

try {
    new SoapClient(null, [
        'location' => 'http://example.com/',
        'uri' => 'urn:test',
        'classmap' => $classmap,
    ]);
} catch (Throwable $e) {
    echo 'SoapClient: ', $e::class, ': ', $e->getMessage(), PHP_EOL;
}

try {
    new SoapServer(null, [
        'uri' => 'urn:test',
        'classmap' => $classmap,
    ]);
} catch (Throwable $e) {
    echo 'SoapServer: ', $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
SoapClient: SoapFault: SoapClient::__construct(): 'classmap' option must be an associative array
SoapServer: ValueError: SoapServer::__construct(): Argument #2 ($options) "classmap" option must be an associative array
--TEST--
SoapClient reports an invalid "classmap" option as a fatal error when exceptions are disabled
--EXTENSIONS--
soap
--FILE--
<?php

new SoapClient(null, [
    'location' => 'http://example.com/',
    'uri' => 'urn:test',
    'exceptions' => false,
    'classmap' => ['type' => 'stdClass', 1 => 'stdClass'],
]);

echo 'not reached', PHP_EOL;

?>
--EXPECTF--
Fatal error: SoapClient::__construct(): 'classmap' option must be an associative array in %s on line %d

Comment thread ext/soap/soap.c Outdated
}
/* }}} */

static bool soap_class_map_is_valid(const HashTable *class_map)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name a bit misleading since you check only the keys

Co-Authored-By: David CARLIER <devnexen@gmail.com>
Comment thread ext/soap/soap.c Outdated
Z_TYPE_P(tmp) == IS_ARRAY) {
if (UNEXPECTED(HT_IS_PACKED(Z_ARRVAL_P(tmp)))) {
if (UNEXPECTED(!soap_class_map_has_only_string_keys(Z_ARRVAL_P(tmp)))) {
php_error_docref(NULL, E_ERROR, "'classmap' option must be an associative array");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we re in the "TODO mindset", what about having a ValueError instead (to match the server side) ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong opinions on this tho. But sure, this makes the behavior of SoapClient and SoapServer the same.

Comment thread ext/soap/soap.c
if (UNEXPECTED(!soap_class_map_has_only_string_keys(Z_ARRVAL_P(tmp)))) {
zend_argument_value_error(2, "\"classmap\" option must be an associative array");
if (context) {
zend_list_delete(context->res);

@LamentXU123 LamentXU123 Jul 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we throw error here, the code won't goes to ZVAL_RES(). So we need to manually delete this.
the context is created with an additional ref:

  context = php_stream_context_from_zval(tmp, 1);
  Z_ADDREF_P(tmp);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants