diff --git a/NEWS b/NEWS
index 30076721441..7729c8ce582 100644
--- a/NEWS
+++ b/NEWS
@@ -49,6 +49,8 @@ PHP NEWS
- Soap:
. Fixed bug #55639 (Digest autentication dont work). (nielsdos)
. Fix SoapFault property destruction. (nielsdos)
+ . Fixed bug GH-15252 (SOAP XML broken since PHP 8.3.9 when using classmap
+ constructor option). (nielsdos)
- Standard:
. Fix passing non-finite timeout values in stream functions. (nielsdos)
diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c
index a5f4caa7c5a..ee3b9ccc9bb 100644
--- a/ext/soap/php_encoding.c
+++ b/ext/soap/php_encoding.c
@@ -449,8 +449,11 @@ 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) {
- 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) {
+ 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) {
/* TODO: namespace isn't stored */
encodePtr enc = NULL;
@@ -1379,6 +1382,7 @@ 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;
}
@@ -3637,48 +3641,3 @@ 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 574ee4942a5..a4e16666e68 100644
--- a/ext/soap/php_encoding.h
+++ b/ext/soap/php_encoding.h
@@ -214,8 +214,6 @@ 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;
diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h
index c30a5a45c3a..33a071ed418 100644
--- a/ext/soap/php_soap.h
+++ b/ext/soap/php_soap.h
@@ -98,7 +98,7 @@ struct _soapService {
char *actor;
char *uri;
xmlCharEncodingHandlerPtr encoding;
- zval class_map;
+ HashTable *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 b4108d5450d..114d3695789 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -837,7 +837,7 @@ PHP_METHOD(SoapServer, __construct)
if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_ARRAY) {
- create_normalized_classmap(&service->class_map, tmp);
+ service->class_map = zend_array_dup(Z_ARRVAL_P(tmp));
}
if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
@@ -1303,7 +1303,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) = Z_ARR(service->class_map);
+ SOAP_GLOBAL(class_map) = service->class_map;
old_typemap = SOAP_GLOBAL(typemap);
SOAP_GLOBAL(typemap) = service->typemap;
old_features = SOAP_GLOBAL(features);
@@ -2003,7 +2003,7 @@ PHP_METHOD(SoapClient, __construct)
}
if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_ARRAY) {
- create_normalized_classmap(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
+ ZVAL_COPY(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
}
if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL &&
@@ -4405,7 +4405,10 @@ static void delete_service(void *data) /* {{{ */
if (service->encoding) {
xmlCharEncCloseFunc(service->encoding);
}
- zval_ptr_dtor(&service->class_map);
+ if (service->class_map) {
+ zend_hash_destroy(service->class_map);
+ FREE_HASHTABLE(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
deleted file mode 100644
index 8c4e6068591..00000000000
--- a/ext/soap/tests/bug69280.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---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
deleted file mode 100644
index 8615ac77cdc..00000000000
--- a/ext/soap/tests/bug69280.wsdl
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-