diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c
index 71bfb961826..177ac1edbf7 100644
--- a/ext/soap/php_encoding.c
+++ b/ext/soap/php_encoding.c
@@ -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);
+}
diff --git a/ext/soap/php_encoding.h b/ext/soap/php_encoding.h
index 67023a81d82..33b64d391b3 100644
--- a/ext/soap/php_encoding.h
+++ b/ext/soap/php_encoding.h
@@ -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;
diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h
index bbb012cfa2e..d8b68b9f387 100644
--- a/ext/soap/php_soap.h
+++ b/ext/soap/php_soap.h
@@ -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;
diff --git a/ext/soap/soap.c b/ext/soap/soap.c
index 3f7f8606875..2f788386d70 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -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);
}
diff --git a/ext/soap/tests/bug69280.phpt b/ext/soap/tests/bug69280.phpt
new file mode 100644
index 00000000000..8c4e6068591
--- /dev/null
+++ b/ext/soap/tests/bug69280.phpt
@@ -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--
+__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--
+
+propprop1
diff --git a/ext/soap/tests/bug69280.wsdl b/ext/soap/tests/bug69280.wsdl
new file mode 100644
index 00000000000..8615ac77cdc
--- /dev/null
+++ b/ext/soap/tests/bug69280.wsdl
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+