From 8e7ff9a0fa067abc1e4f827563e1db8bb0a5fe16 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Jul 2025 17:04:59 +0100 Subject: [PATCH 1/6] ext/sopa: call_user_function API doesn't care about the function table --- ext/soap/soap.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 0786ac811059..945366204315 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1542,11 +1542,7 @@ PHP_METHOD(SoapServer, handle) if (zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)) != NULL || ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - if (service->type == SOAP_CLASS || service->type == SOAP_OBJECT) { - call_status = call_user_function(NULL, soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters); - } else { - call_status = call_user_function(EG(function_table), NULL, &h->function_name, &h->retval, h->num_params, h->parameters); - } + call_status = call_user_function(NULL, soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters); if (call_status != SUCCESS) { php_error_docref(NULL, E_WARNING, "Function '%s' call failed", Z_STRVAL(h->function_name)); return; @@ -1572,16 +1568,12 @@ PHP_METHOD(SoapServer, handle) if (zend_hash_find_ptr_lc(function_table, Z_STR(function_name)) != NULL || ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - if (service->type == SOAP_CLASS || service->type == SOAP_OBJECT) { - call_status = call_user_function(NULL, soap_obj, &function_name, &retval, num_params, params); - if (service->type == SOAP_CLASS) { - if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - soap_obj = NULL; - } + call_status = call_user_function(NULL, soap_obj, &function_name, &retval, num_params, params); + if (service->type == SOAP_CLASS) { + if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + zval_ptr_dtor(soap_obj); + soap_obj = NULL; } - } else { - call_status = call_user_function(EG(function_table), NULL, &function_name, &retval, num_params, params); } } else { php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name)); From 249168bf0ee43f2f92f4347289c77c93efaa7af3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Jul 2025 17:41:19 +0100 Subject: [PATCH 2/6] ext/soap: Use bool type instead of int type for functions_all field --- ext/soap/php_soap.h | 2 +- ext/soap/soap.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ext/soap/php_soap.h b/ext/soap/php_soap.h index 1eea30c62e90..f97a3fe75110 100644 --- a/ext/soap/php_soap.h +++ b/ext/soap/php_soap.h @@ -76,7 +76,7 @@ struct _soapService { struct _soap_functions { HashTable *ft; - int functions_all; + bool functions_all; } soap_functions; struct _soap_class { diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 945366204315..bad2c5a6c2da 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1008,7 +1008,7 @@ PHP_METHOD(SoapServer, __construct) service->version = version; service->type = SOAP_FUNCTIONS; - service->soap_functions.functions_all = FALSE; + service->soap_functions.functions_all = false; service->soap_functions.ft = zend_new_array(0); if (wsdl) { @@ -1136,7 +1136,7 @@ PHP_METHOD(SoapServer, getFunctions) ft = &(Z_OBJCE(service->soap_object)->function_table); } else if (service->type == SOAP_CLASS) { ft = &service->soap_class.ce->function_table; - } else if (service->soap_functions.functions_all == TRUE) { + } else if (service->soap_functions.functions_all) { ft = EG(function_table); } else if (service->soap_functions.ft != NULL) { zval *name; @@ -1177,7 +1177,7 @@ PHP_METHOD(SoapServer, addFunction) zval *tmp_function; if (service->soap_functions.ft == NULL) { - service->soap_functions.functions_all = FALSE; + service->soap_functions.functions_all = false; service->soap_functions.ft = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(function_name))); } @@ -1216,7 +1216,7 @@ PHP_METHOD(SoapServer, addFunction) RETURN_THROWS(); } if (service->soap_functions.ft == NULL) { - service->soap_functions.functions_all = FALSE; + service->soap_functions.functions_all = false; service->soap_functions.ft = zend_new_array(0); } @@ -1235,7 +1235,7 @@ PHP_METHOD(SoapServer, addFunction) efree(service->soap_functions.ft); service->soap_functions.ft = NULL; } - service->soap_functions.functions_all = TRUE; + service->soap_functions.functions_all = true; } else { zend_argument_value_error(1, "must be SOAP_FUNCTIONS_ALL when an integer is passed"); } @@ -1516,7 +1516,7 @@ PHP_METHOD(SoapServer, handle) } function_table = &((Z_OBJCE_P(soap_obj))->function_table); } else { - if (service->soap_functions.functions_all == TRUE) { + if (service->soap_functions.functions_all) { function_table = EG(function_table); } else { function_table = service->soap_functions.ft; From a01ff380d2751aa70dab53fa939b5fbeb366a7cc Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Jul 2025 17:51:16 +0100 Subject: [PATCH 3/6] ext/soap: store zend_function pointers in soap_functions.ft field --- ext/soap/soap.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index bad2c5a6c2da..787b17ac5d67 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1009,7 +1009,9 @@ PHP_METHOD(SoapServer, __construct) service->version = version; service->type = SOAP_FUNCTIONS; service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(0); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, 0, NULL, NULL, false); if (wsdl) { service->sdl = get_sdl(ZEND_THIS, ZSTR_VAL(wsdl), cache_wsdl); @@ -1123,7 +1125,7 @@ PHP_METHOD(SoapServer, setObject) PHP_METHOD(SoapServer, getFunctions) { soapServicePtr service; - HashTable *ft = NULL; + const HashTable *ft = NULL; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -1139,11 +1141,7 @@ PHP_METHOD(SoapServer, getFunctions) } else if (service->soap_functions.functions_all) { ft = EG(function_table); } else if (service->soap_functions.ft != NULL) { - zval *name; - - ZEND_HASH_MAP_FOREACH_VAL(service->soap_functions.ft, name) { - add_next_index_str(return_value, zend_string_copy(Z_STR_P(name))); - } ZEND_HASH_FOREACH_END(); + ft = service->soap_functions.ft; } if (ft != NULL) { zend_function *f; @@ -1162,7 +1160,7 @@ PHP_METHOD(SoapServer, getFunctions) PHP_METHOD(SoapServer, addFunction) { soapServicePtr service; - zval *function_name, function_copy; + zval *function_name; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &function_name) == FAILURE) { RETURN_THROWS(); @@ -1178,7 +1176,9 @@ PHP_METHOD(SoapServer, addFunction) if (service->soap_functions.ft == NULL) { service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(function_name))); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, zend_hash_num_elements(Z_ARRVAL_P(function_name)), NULL, NULL, false); } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(function_name), tmp_function) { @@ -1191,15 +1191,15 @@ PHP_METHOD(SoapServer, addFunction) } key = zend_string_tolower(Z_STR_P(tmp_function)); + f = zend_hash_find_ptr(EG(function_table), key); - if ((f = zend_hash_find_ptr(EG(function_table), key)) == NULL) { + if (f == NULL) { zend_string_release_ex(key, false); zend_type_error("SoapServer::addFunction(): Function \"%s\" not found", Z_STRVAL_P(tmp_function)); RETURN_THROWS(); } - ZVAL_STR_COPY(&function_copy, f->common.function_name); - zend_hash_update(service->soap_functions.ft, key, &function_copy); + zend_hash_update_ptr(service->soap_functions.ft, key, f); zend_string_release_ex(key, 0); } ZEND_HASH_FOREACH_END(); @@ -1209,19 +1209,20 @@ PHP_METHOD(SoapServer, addFunction) zend_function *f; key = zend_string_tolower(Z_STR_P(function_name)); - - if ((f = zend_hash_find_ptr(EG(function_table), key)) == NULL) { + f = zend_hash_find_ptr(EG(function_table), key); + if (f == NULL) { zend_string_release_ex(key, false); zend_argument_type_error(1, "must be a valid function name, function \"%s\" not found", Z_STRVAL_P(function_name)); RETURN_THROWS(); } if (service->soap_functions.ft == NULL) { service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(0); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, 0, NULL, NULL, false); } - ZVAL_STR_COPY(&function_copy, f->common.function_name); - zend_hash_update(service->soap_functions.ft, key, &function_copy); + zend_hash_update_ptr(service->soap_functions.ft, key, f); zend_string_release_ex(key, 0); } else if (Z_TYPE_P(function_name) == IS_LONG) { if (Z_LVAL_P(function_name) == SOAP_FUNCTIONS_ALL) { From a955ed19bc9ee6b3d95539c477d980522d0267aa Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Jul 2025 17:36:45 +0100 Subject: [PATCH 4/6] ext/soap: call userland functions directly --- ext/soap/soap.c | 127 ++++++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 787b17ac5d67..d1a47506b070 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1274,12 +1274,11 @@ PHP_METHOD(SoapServer, handle) sdlPtr old_sdl = NULL; soapServicePtr service; xmlDocPtr doc_request = NULL, doc_return = NULL; - zval function_name, *params, *soap_obj, retval; + zval function_name, *params, retval; char cont_len[30]; uint32_t num_params = 0; - int size, i, call_status = 0; + int size, i; xmlChar *buf; - HashTable *function_table; soapHeader *soap_headers = NULL; sdlFunctionPtr function; char *arg = NULL; @@ -1454,10 +1453,15 @@ PHP_METHOD(SoapServer, handle) service->soap_headers_ptr = &soap_headers; - soap_obj = NULL; + zval *soap_obj = NULL; + zend_object *soap_zobj = NULL; + zend_class_entry *soap_obj_ce = NULL; + HashTable *function_table; if (service->type == SOAP_OBJECT) { soap_obj = &service->soap_object; - function_table = &((Z_OBJCE_P(soap_obj))->function_table); + soap_zobj = Z_OBJ_P(soap_obj); + soap_obj_ce = Z_OBJCE_P(soap_obj); + function_table = &soap_obj_ce->function_table; } else if (service->type == SOAP_CLASS) { /* If persistent then set soap_obj from the previous created session (if available) */ #ifdef SOAP_HAS_SESSION_SUPPORT @@ -1515,7 +1519,9 @@ PHP_METHOD(SoapServer, handle) soap_obj = &tmp_soap; } } - function_table = &((Z_OBJCE_P(soap_obj))->function_table); + soap_zobj = Z_OBJ_P(soap_obj); + soap_obj_ce = Z_OBJCE_P(soap_obj); + function_table = &soap_obj_ce->function_table; } else { if (service->soap_functions.functions_all) { function_table = EG(function_table); @@ -1540,44 +1546,52 @@ PHP_METHOD(SoapServer, handle) } } #endif - if (zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)) != NULL || - ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && - zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - call_status = call_user_function(NULL, soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters); - if (call_status != SUCCESS) { - php_error_docref(NULL, E_WARNING, "Function '%s' call failed", Z_STRVAL(h->function_name)); - return; + zend_function *header_fn = zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)); + if (UNEXPECTED(header_fn == NULL)) { + if (soap_obj_ce && soap_obj_ce->__call) { + header_fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false); + goto soap_header_func_call; } - if (Z_TYPE(h->retval) == IS_OBJECT && - instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) { - php_output_discard(); - soap_server_fault_ex(function, &h->retval, h); - if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} - goto fail; - } else if (EG(exception)) { - php_output_discard(); - _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} + if (h->mustUnderstand) { + soap_server_fault_en("MustUnderstand","Header not understood", NULL, NULL, NULL); goto fail; } - } else if (h->mustUnderstand) { - soap_server_fault_en("MustUnderstand","Header not understood", NULL, NULL, NULL); + continue; + } + +soap_header_func_call: + zend_call_known_function(header_fn, soap_zobj, soap_obj_ce, &h->retval, h->num_params, h->parameters, NULL); + if (Z_TYPE(h->retval) == IS_OBJECT && + instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) { + php_output_discard(); + soap_server_fault_ex(function, &h->retval, h); + if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} + goto fail; + } else if (EG(exception)) { + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} + goto fail; } } } - if (zend_hash_find_ptr_lc(function_table, Z_STR(function_name)) != NULL || - ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && - zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - call_status = call_user_function(NULL, soap_obj, &function_name, &retval, num_params, params); - if (service->type == SOAP_CLASS) { - if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - soap_obj = NULL; - } + zend_function *fn = zend_hash_find_ptr_lc(function_table, Z_STR(function_name)); + if (UNEXPECTED(fn == NULL)) { + if (soap_obj_ce && soap_obj_ce->__call) { + fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false); + } else { + php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name)); + goto fail; + } + } + zend_call_known_function(fn, soap_zobj, soap_obj_ce, &retval, num_params, params, NULL); + + if (service->type == SOAP_CLASS) { + if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + zval_ptr_dtor(soap_obj); + soap_obj = NULL; } - } else { - php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name)); } if (EG(exception)) { @@ -1593,32 +1607,27 @@ PHP_METHOD(SoapServer, handle) goto fail; } - if (call_status == SUCCESS) { - char *response_name; - - if (Z_TYPE(retval) == IS_OBJECT && - instanceof_function(Z_OBJCE(retval), soap_fault_class_entry)) { - php_output_discard(); - soap_server_fault_ex(function, &retval, NULL); - goto fail; - } + char *response_name; - bool has_response_name = function && function->responseName; - if (has_response_name) { - response_name = function->responseName; - } else { - response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response")); - memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name)); - memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response")); - } - doc_return = serialize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version); + if (Z_TYPE(retval) == IS_OBJECT && + instanceof_function(Z_OBJCE(retval), soap_fault_class_entry)) { + php_output_discard(); + soap_server_fault_ex(function, &retval, NULL); + goto fail; + } - if (!has_response_name) { - efree(response_name); - } + bool has_response_name = function && function->responseName; + if (has_response_name) { + response_name = function->responseName; } else { - php_error_docref(NULL, E_WARNING, "Function '%s' call failed", Z_STRVAL(function_name)); - return; + response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response")); + memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name)); + memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response")); + } + doc_return = serialize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version); + + if (!has_response_name) { + efree(response_name); } if (EG(exception)) { From cdb029dd089a8f67f2cbbe19d7016473ad2a5a66 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Jul 2025 18:19:38 +0100 Subject: [PATCH 5/6] ext/soap: throw an Error for undefined functions/methods --- ext/soap/soap.c | 11 +++++++++-- ext/soap/tests/bugs/bug73037.phpt | 29 ++++++++++++++--------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index d1a47506b070..9cbbf20f5914 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1485,7 +1485,7 @@ PHP_METHOD(SoapServer, handle) } #endif - /* If new session or something weird happned */ + /* If new session or something weird happened */ if (soap_obj == NULL) { object_init_ex(&tmp_soap, service->soap_class.ce); @@ -1581,7 +1581,14 @@ PHP_METHOD(SoapServer, handle) if (soap_obj_ce && soap_obj_ce->__call) { fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false); } else { - php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name)); + if (soap_obj_ce) { + zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(soap_obj_ce->name), Z_STRVAL(function_name)); + } else { + zend_throw_error(NULL, "Call to undefined function %s()", Z_STRVAL(function_name)); + } + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} goto fail; } } diff --git a/ext/soap/tests/bugs/bug73037.phpt b/ext/soap/tests/bugs/bug73037.phpt index 7a5b99776772..cfa6d7fd54e8 100644 --- a/ext/soap/tests/bugs/bug73037.phpt +++ b/ext/soap/tests/bugs/bug73037.phpt @@ -136,43 +136,42 @@ cleanup: --EXPECT-- Iteration 0 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 1 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 2 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 3 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 4 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 5 -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() Iteration 6 -Function 'CATALOG' doesn't exist - -Function 'CATALOG' doesn't exist +Call to undefined method stdClass::CATALOG() +Call to undefined method stdClass::CATALOG() From f55a976f67b1466945b84c73dff34f5292afc381 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 7 Jul 2025 10:54:53 +0100 Subject: [PATCH 6/6] restructure to get rid of goto --- ext/soap/soap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 9cbbf20f5914..681c7b72d559 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1547,11 +1547,11 @@ PHP_METHOD(SoapServer, handle) } #endif zend_function *header_fn = zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)); + /* If object has a __call() magic method use it */ + if (header_fn == NULL && soap_obj_ce && soap_obj_ce->__call) { + header_fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false); + } if (UNEXPECTED(header_fn == NULL)) { - if (soap_obj_ce && soap_obj_ce->__call) { - header_fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false); - goto soap_header_func_call; - } if (h->mustUnderstand) { soap_server_fault_en("MustUnderstand","Header not understood", NULL, NULL, NULL); goto fail; @@ -1559,7 +1559,6 @@ PHP_METHOD(SoapServer, handle) continue; } -soap_header_func_call: zend_call_known_function(header_fn, soap_zobj, soap_obj_ce, &h->retval, h->num_params, h->parameters, NULL); if (Z_TYPE(h->retval) == IS_OBJECT && instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) {