Skip to content

Commit 61191dc

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix bug #69280: SoapClient classmap doesn't support fully qualified class name (#14398)
2 parents 8dc2391 + d11a3c6 commit 61191dc

File tree

6 files changed

+144
-14
lines changed

6 files changed

+144
-14
lines changed

ext/soap/php_encoding.c

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,8 @@ static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xml
457457
zend_string *type_name;
458458

459459
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(SOAP_GLOBAL(class_map), type_name, tmp) {
460-
ZVAL_DEREF(tmp);
461-
if (Z_TYPE_P(tmp) == IS_STRING &&
462-
ZSTR_LEN(ce->name) == Z_STRLEN_P(tmp) &&
463-
zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0 &&
464-
type_name) {
460+
if (ZSTR_LEN(ce->name) == Z_STRLEN_P(tmp) &&
461+
zend_binary_strncasecmp(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), Z_STRVAL_P(tmp), ZSTR_LEN(ce->name), ZSTR_LEN(ce->name)) == 0) {
465462

466463
/* TODO: namespace isn't stored */
467464
encodePtr enc = NULL;
@@ -1395,7 +1392,6 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z
13951392
classname = zend_hash_str_find_deref(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str));
13961393
}
13971394
if (classname != NULL &&
1398-
Z_TYPE_P(classname) == IS_STRING &&
13991395
(tmp = zend_fetch_class(Z_STR_P(classname), ZEND_FETCH_CLASS_AUTO)) != NULL) {
14001396
ce = tmp;
14011397
}
@@ -3668,3 +3664,48 @@ void delete_encoder_persistent(zval *zv)
36683664
assert(t->details.map == NULL);
36693665
free(t);
36703666
}
3667+
3668+
/* Normalize leading backslash similarly to how the engine strips it away. */
3669+
static inline zend_string *drop_leading_backslash(zend_string *str) {
3670+
if (ZSTR_VAL(str)[0] == '\\') {
3671+
return zend_string_init(ZSTR_VAL(str) + 1, ZSTR_LEN(str) - 1, false);
3672+
} else {
3673+
return zend_string_copy(str);
3674+
}
3675+
}
3676+
3677+
static HashTable *create_normalized_classmap_copy(HashTable *class_map)
3678+
{
3679+
HashTable *normalized = zend_new_array(zend_hash_num_elements(class_map));
3680+
3681+
zend_string *key;
3682+
zval *value;
3683+
ZEND_HASH_FOREACH_STR_KEY_VAL(class_map, key, value) {
3684+
ZVAL_DEREF(value);
3685+
3686+
if (key != NULL && Z_TYPE_P(value) == IS_STRING) {
3687+
zval zv;
3688+
ZVAL_STR(&zv, drop_leading_backslash(Z_STR_P(value)));
3689+
zend_hash_add_new(normalized, key, &zv);
3690+
}
3691+
} ZEND_HASH_FOREACH_END();
3692+
3693+
return normalized;
3694+
}
3695+
3696+
void create_normalized_classmap(zval *return_value, zval *class_map)
3697+
{
3698+
/* Check if we need to make a copy. */
3699+
zend_string *key;
3700+
zval *value;
3701+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(class_map), key, value) {
3702+
if (key == NULL || Z_TYPE_P(value) != IS_STRING || ZSTR_VAL(Z_STR_P(value))[0] == '\\') {
3703+
/* TODO: should probably throw in some of these cases to indicate programmer error,
3704+
* e.g. in the case where a non-string (after dereferencing) is provided. */
3705+
RETURN_ARR(create_normalized_classmap_copy(Z_ARR_P(class_map)));
3706+
}
3707+
} ZEND_HASH_FOREACH_END();
3708+
3709+
/* We didn't have to make an actual copy, just increment the refcount. */
3710+
RETURN_COPY(class_map);
3711+
}

ext/soap/php_encoding.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ encodePtr get_conversion(int encode);
215215
void delete_encoder(zval *zv);
216216
void delete_encoder_persistent(zval *zv);
217217

218+
void create_normalized_classmap(zval *return_value, zval *class_map);
219+
218220
extern encode defaultEncoding[];
219221
extern int numDefaultEncodings;
220222

ext/soap/php_soap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ struct _soapService {
9696
char *actor;
9797
char *uri;
9898
xmlCharEncodingHandlerPtr encoding;
99-
HashTable *class_map;
99+
zval class_map;
100100
int features;
101101
struct _soapHeader **soap_headers_ptr;
102102
int send_errors;

ext/soap/soap.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ PHP_METHOD(SoapServer, __construct)
923923

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

929929
if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
@@ -1395,7 +1395,7 @@ PHP_METHOD(SoapServer, handle)
13951395
old_encoding = SOAP_GLOBAL(encoding);
13961396
SOAP_GLOBAL(encoding) = service->encoding;
13971397
old_class_map = SOAP_GLOBAL(class_map);
1398-
SOAP_GLOBAL(class_map) = service->class_map;
1398+
SOAP_GLOBAL(class_map) = Z_ARR(service->class_map);
13991399
old_typemap = SOAP_GLOBAL(typemap);
14001400
SOAP_GLOBAL(typemap) = service->typemap;
14011401
old_features = SOAP_GLOBAL(features);
@@ -2084,7 +2084,7 @@ PHP_METHOD(SoapClient, __construct)
20842084
}
20852085
if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
20862086
Z_TYPE_P(tmp) == IS_ARRAY) {
2087-
ZVAL_COPY(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
2087+
create_normalized_classmap(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
20882088
}
20892089

20902090
if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
@@ -4484,10 +4484,7 @@ static void delete_service(soapServicePtr service) /* {{{ */
44844484
if (service->encoding) {
44854485
xmlCharEncCloseFunc(service->encoding);
44864486
}
4487-
if (service->class_map) {
4488-
zend_hash_destroy(service->class_map);
4489-
FREE_HASHTABLE(service->class_map);
4490-
}
4487+
zval_ptr_dtor(&service->class_map);
44914488
zval_ptr_dtor(&service->soap_object);
44924489
efree(service);
44934490
}

ext/soap/tests/bug69280.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Bug #69280 (SoapClient classmap doesn't support fully qualified class name)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--CREDITS--
8+
champetier dot etienne at gmail dot com
9+
--FILE--
10+
<?php
11+
abstract class AbstractClass {
12+
public $prop;
13+
}
14+
15+
class RealClass1 extends AbstractClass {
16+
public $prop1;
17+
}
18+
19+
class TestWS extends \SoapClient {
20+
public function TestMethod($parameters) {
21+
return $this->__soapCall('TestMethod', [$parameters], [
22+
'uri' => 'http://tempuri.org/',
23+
'soapaction' => ''
24+
]
25+
);
26+
}
27+
28+
public function __doRequest(string $request, string $location, string $action, int $version, bool $oneWay = false): ?string {
29+
die($request);
30+
}
31+
}
32+
33+
$a = new TestWS(__DIR__ . '/bug69280.wsdl', ['classmap' => [
34+
'AbstractClass' => '\AbstractClass',
35+
'RealClass1' => '\RealClass1',
36+
]]);
37+
$r1 = new \RealClass1();
38+
$r1->prop = "prop";
39+
$r1->prop1 = "prop1";
40+
$a->TestMethod($r1);
41+
?>
42+
--EXPECT--
43+
<?xml version="1.0" encoding="UTF-8"?>
44+
<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>

ext/soap/tests/bug69280.wsdl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<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/">
3+
<types>
4+
<xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
5+
<xs:complexType name="AbstractClass">
6+
<xs:sequence>
7+
<xs:element minOccurs="0" maxOccurs="1" name="prop" type="xs:string" />
8+
</xs:sequence>
9+
</xs:complexType>
10+
<xs:complexType name="RealClass1">
11+
<xs:complexContent mixed="false">
12+
<xs:extension base="s0:AbstractClass">
13+
<xs:sequence>
14+
<xs:element minOccurs="0" maxOccurs="1" name="prop1" type="xs:string" />
15+
</xs:sequence>
16+
</xs:extension>
17+
</xs:complexContent>
18+
</xs:complexType>
19+
</xs:schema>
20+
</types>
21+
<message name="TestMethodSoapIn">
22+
<part name="parameters" element="RealClass1" />
23+
</message>
24+
<portType name="TestWSSoap">
25+
<operation name="TestMethod">
26+
<input message="s0:TestMethodSoapIn" />
27+
</operation>
28+
</portType>
29+
<binding name="TestWSSoap" type="s0:TestWSSoap">
30+
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
31+
<operation name="TestMethod">
32+
<soap:operation soapAction="http://tempuri.org/TestMethod" style="document" />
33+
<input>
34+
<soap:body use="literal" />
35+
</input>
36+
<output>
37+
<soap:body use="literal" />
38+
</output>
39+
</operation>
40+
</binding>
41+
<service name="TestWS">
42+
<port name="TestWSSoap" binding="s0:TestWSSoap">
43+
<soap:address location="http://tempuri.org/" />
44+
</port>
45+
</service>
46+
</definitions>

0 commit comments

Comments
 (0)