Skip to content

Fix bug #69280: SoapClient classmap doesn't support fully qualified class name #14398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ PHP NEWS
. Fix memory leak if calling SoapServer::setClass() twice. (nielsdos)
. Fix reading zlib ini settings in ext-soap. (nielsdos)
. Fix memory leaks with string function name lookups. (nielsdos)
. Fixed bug #69280 (SoapClient classmap doesn't support fully qualified class
name). (nielsdos)

- Sodium:
. Fix memory leaks in ext/sodium on failure of some functions. (nielsdos)
Expand Down
53 changes: 47 additions & 6 deletions ext/soap/php_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,8 @@ static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xml
zend_string *type_name;

ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(SOAP_GLOBAL(class_map), type_name, tmp) {
ZVAL_DEREF(tmp);
if (Z_TYPE_P(tmp) == IS_STRING &&
ZSTR_LEN(ce->name) == Z_STRLEN_P(tmp) &&
zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0 &&
type_name) {
if (ZSTR_LEN(ce->name) == Z_STRLEN_P(tmp) &&
zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0) {

/* TODO: namespace isn't stored */
encodePtr enc = NULL;
Expand Down Expand Up @@ -1382,7 +1379,6 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z
zend_class_entry *tmp;

if ((classname = zend_hash_str_find_deref(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str))) != NULL &&
Z_TYPE_P(classname) == IS_STRING &&
(tmp = zend_fetch_class(Z_STR_P(classname), ZEND_FETCH_CLASS_AUTO)) != NULL) {
ce = tmp;
}
Expand Down Expand Up @@ -3629,3 +3625,48 @@ void delete_encoder_persistent(zval *zv)
assert(t->details.map == NULL);
free(t);
}

/* Normalize leading backslash similarly to how the engine strips it away. */
static inline zend_string *drop_leading_backslash(zend_string *str) {
if (ZSTR_VAL(str)[0] == '\\') {
return zend_string_init(ZSTR_VAL(str) + 1, ZSTR_LEN(str) - 1, false);
} else {
return zend_string_copy(str);
}
}

static HashTable *create_normalized_classmap_copy(HashTable *class_map)
{
HashTable *normalized = zend_new_array(zend_hash_num_elements(class_map));

zend_string *key;
zval *value;
ZEND_HASH_FOREACH_STR_KEY_VAL(class_map, key, value) {
ZVAL_DEREF(value);

if (key != NULL && Z_TYPE_P(value) == IS_STRING) {
zval zv;
ZVAL_STR(&zv, drop_leading_backslash(Z_STR_P(value)));
zend_hash_add_new(normalized, key, &zv);
}
} ZEND_HASH_FOREACH_END();

return normalized;
}

void create_normalized_classmap(zval *return_value, zval *class_map)
{
/* Check if we need to make a copy. */
zend_string *key;
zval *value;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(class_map), key, value) {
if (key == NULL || Z_TYPE_P(value) != IS_STRING || ZSTR_VAL(Z_STR_P(value))[0] == '\\') {
/* TODO: should probably throw in some of these cases to indicate programmer error,
* e.g. in the case where a non-string (after dereferencing) is provided. */
RETURN_ARR(create_normalized_classmap_copy(Z_ARR_P(class_map)));
}
} ZEND_HASH_FOREACH_END();

/* We didn't have to make an actual copy, just increment the refcount. */
RETURN_COPY(class_map);
}
2 changes: 2 additions & 0 deletions ext/soap/php_encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ encodePtr get_conversion(int encode);
void delete_encoder(zval *zv);
void delete_encoder_persistent(zval *zv);

void create_normalized_classmap(zval *return_value, zval *class_map);

extern const encode defaultEncoding[];
extern int numDefaultEncodings;

Expand Down
2 changes: 1 addition & 1 deletion ext/soap/php_soap.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct _soapService {
char *actor;
char *uri;
xmlCharEncodingHandlerPtr encoding;
HashTable *class_map;
zval class_map;
int features;
struct _soapHeader **soap_headers_ptr;
int send_errors;
Expand Down
11 changes: 4 additions & 7 deletions ext/soap/soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ PHP_METHOD(SoapServer, __construct)

if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_ARRAY) {
service->class_map = zend_array_dup(Z_ARRVAL_P(tmp));
create_normalized_classmap(&service->class_map, tmp);
}

if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
Expand Down Expand Up @@ -1282,7 +1282,7 @@ PHP_METHOD(SoapServer, handle)
old_encoding = SOAP_GLOBAL(encoding);
SOAP_GLOBAL(encoding) = service->encoding;
old_class_map = SOAP_GLOBAL(class_map);
SOAP_GLOBAL(class_map) = service->class_map;
SOAP_GLOBAL(class_map) = Z_ARR(service->class_map);
old_typemap = SOAP_GLOBAL(typemap);
SOAP_GLOBAL(typemap) = service->typemap;
old_features = SOAP_GLOBAL(features);
Expand Down Expand Up @@ -1982,7 +1982,7 @@ PHP_METHOD(SoapClient, __construct)
}
if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_ARRAY) {
ZVAL_COPY(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
create_normalized_classmap(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
}

if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
Expand Down Expand Up @@ -4384,10 +4384,7 @@ static void delete_service(void *data) /* {{{ */
if (service->encoding) {
xmlCharEncCloseFunc(service->encoding);
}
if (service->class_map) {
zend_hash_destroy(service->class_map);
FREE_HASHTABLE(service->class_map);
}
zval_ptr_dtor(&service->class_map);
zval_ptr_dtor(&service->soap_object);
efree(service);
}
Expand Down
44 changes: 44 additions & 0 deletions ext/soap/tests/bug69280.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
Bug #69280 (SoapClient classmap doesn't support fully qualified class name)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--CREDITS--
champetier dot etienne at gmail dot com
--FILE--
<?php
abstract class AbstractClass {
public $prop;
}

class RealClass1 extends AbstractClass {
public $prop1;
}

class TestWS extends \SoapClient {
public function TestMethod($parameters) {
return $this->__soapCall('TestMethod', [$parameters], [
'uri' => 'http://tempuri.org/',
'soapaction' => ''
]
);
}

public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string {
die($request);
}
}

$a = new TestWS(__DIR__ . '/bug69280.wsdl', ['classmap' => [
'AbstractClass' => '\AbstractClass',
'RealClass1' => '\RealClass1',
]]);
$r1 = new \RealClass1();
$r1->prop = "prop";
$r1->prop1 = "prop1";
$a->TestMethod($r1);
?>
--EXPECT--
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SOAP-ENV:Body><parameters xsi:type="ns1:RealClass1"><ns1:prop>prop</ns1:prop><ns1:prop1>prop1</ns1:prop1></parameters></SOAP-ENV:Body></SOAP-ENV:Envelope>
46 changes: 46 additions & 0 deletions ext/soap/tests/bug69280.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:s0="http://tempuri.org/" name="TestWS" targetNamespace="http://tempuri.org/" xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="AbstractClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="prop" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="RealClass1">
<xs:complexContent mixed="false">
<xs:extension base="s0:AbstractClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="prop1" type="xs:string" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
</types>
<message name="TestMethodSoapIn">
<part name="parameters" element="RealClass1" />
</message>
<portType name="TestWSSoap">
<operation name="TestMethod">
<input message="s0:TestMethodSoapIn" />
</operation>
</portType>
<binding name="TestWSSoap" type="s0:TestWSSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="TestMethod">
<soap:operation soapAction="http://tempuri.org/TestMethod" style="document" />
<input>
<soap:body use="literal" />
</input>
<output>
<soap:body use="literal" />
</output>
</operation>
</binding>
<service name="TestWS">
<port name="TestWSSoap" binding="s0:TestWSSoap">
<soap:address location="http://tempuri.org/" />
</port>
</service>
</definitions>
Loading