Merge branch 'PHP-8.3'

* PHP-8.3:
  Fix bug #69280: SoapClient classmap doesn't support fully qualified class name (#14398)
This commit is contained in:
Niels Dossche 2024-06-01 13:32:58 +02:00
commit 61191dc313
No known key found for this signature in database
GPG Key ID: B8A8AD166DF0E2E5
6 changed files with 144 additions and 14 deletions

View File

@ -457,11 +457,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;
@ -1395,7 +1392,6 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z
classname = zend_hash_str_find_deref(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str));
}
if (classname != NULL &&
Z_TYPE_P(classname) == IS_STRING &&
(tmp = zend_fetch_class(Z_STR_P(classname), ZEND_FETCH_CLASS_AUTO)) != NULL) {
ce = tmp;
}
@ -3668,3 +3664,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);
}

View File

@ -215,6 +215,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 encode defaultEncoding[];
extern int numDefaultEncodings;

View File

@ -96,7 +96,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;

View File

@ -923,7 +923,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 &&
@ -1395,7 +1395,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);
@ -2084,7 +2084,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 &&
@ -4484,10 +4484,7 @@ static void delete_service(soapServicePtr service) /* {{{ */
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);
}

View File

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

View File

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