diff --git a/ext/soap/soap.c b/ext/soap/soap.c index d8f72e0191aa..d4845873e689 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -933,103 +933,157 @@ static HashTable* soap_create_typemap(sdlPtr sdl, HashTable *ht) /* {{{ */ /* {{{ SoapServer constructor */ PHP_METHOD(SoapServer, __construct) { - soapServicePtr service; - zval *options = NULL; + HashTable *options = NULL; zend_string *wsdl; int version = SOAP_1_1; zend_long cache_wsdl; HashTable *typemap_ht = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!|a", &wsdl, &options) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!|h", &wsdl, &options) == FAILURE) { RETURN_THROWS(); } - SOAP_SERVER_BEGIN_CODE(); - - service = emalloc(sizeof(soapService)); + soapServicePtr service = emalloc(sizeof(soapService)); memset(service, 0, sizeof(soapService)); - service->send_errors = 1; + service->send_errors = true; cache_wsdl = SOAP_GLOBAL(cache_enabled) ? SOAP_GLOBAL(cache_mode) : 0; if (options != NULL) { - HashTable *ht = Z_ARRVAL_P(options); - zval *tmp; - - if ((tmp = zend_hash_str_find(ht, "soap_version", sizeof("soap_version")-1)) != NULL) { - if (Z_TYPE_P(tmp) == IS_LONG && - (Z_LVAL_P(tmp) == SOAP_1_1 || Z_LVAL_P(tmp) == SOAP_1_2)) { - version = Z_LVAL_P(tmp); - } else { - php_error_docref(NULL, E_ERROR, "'soap_version' option must be SOAP_1_1 or SOAP_1_2"); + const zval *soap_version_zv = zend_hash_str_find(options, ZEND_STRL("soap_version")); + if (soap_version_zv) { + if (UNEXPECTED(Z_TYPE_P(soap_version_zv) != IS_LONG)) { + zend_argument_type_error(2, "\"soap_version\" option must be of type int, %s given", zend_zval_type_name(soap_version_zv)); + goto cleanup; } + zend_long soap_version = Z_LVAL_P(soap_version_zv); + if (UNEXPECTED(soap_version != SOAP_1_1 && soap_version != SOAP_1_2)) { + zend_argument_value_error(2, "\"soap_version\" option must be SOAP_1_1 or SOAP_1_2"); + goto cleanup; + } + version = soap_version; } - if ((tmp = zend_hash_str_find(ht, "uri", sizeof("uri")-1)) != NULL && - Z_TYPE_P(tmp) == IS_STRING) { - service->uri = estrndup(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + const zval *uri_zv = zend_hash_str_find(options, ZEND_STRL("uri")); + if (uri_zv) { + if (UNEXPECTED(Z_TYPE_P(uri_zv) != IS_STRING)) { + zend_argument_type_error(2, "\"uri\" option must be of type string, %s given", zend_zval_type_name(uri_zv)); + goto cleanup; + } + service->uri = estrndup(Z_STRVAL_P(uri_zv), Z_STRLEN_P(uri_zv)); } else if (!wsdl) { - php_error_docref(NULL, E_ERROR, "'uri' option is required in nonWSDL mode"); + zend_argument_value_error(2, "\"uri\" option is required when argument #1 ($wsdl) is null"); + goto cleanup; } - if ((tmp = zend_hash_str_find(ht, "actor", sizeof("actor")-1)) != NULL && - Z_TYPE_P(tmp) == IS_STRING) { - service->actor = estrndup(Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + const zval *actor_zv = zend_hash_str_find(options, ZEND_STRL("actor")); + if (actor_zv) { + if (UNEXPECTED(Z_TYPE_P(actor_zv) != IS_STRING)) { + zend_argument_type_error(2, "\"actor\" option must be of type string, %s given", zend_zval_type_name(actor_zv)); + goto cleanup; + } + service->actor = estrndup(Z_STRVAL_P(actor_zv), Z_STRLEN_P(actor_zv)); } - if ((tmp = zend_hash_str_find(ht, "encoding", sizeof("encoding")-1)) != NULL && - Z_TYPE_P(tmp) == IS_STRING) { - xmlCharEncodingHandlerPtr encoding; + const zval *encoding_zv = zend_hash_str_find(options, ZEND_STRL("encoding")); + if (encoding_zv) { + if (UNEXPECTED(Z_TYPE_P(encoding_zv) != IS_STRING)) { + zend_argument_type_error(2, "\"encoding\" option must be of type string, %s given", zend_zval_type_name(encoding_zv)); + goto cleanup; + } - encoding = xmlFindCharEncodingHandler(Z_STRVAL_P(tmp)); - if (encoding == NULL) { - php_error_docref(NULL, E_ERROR, "Invalid 'encoding' option - '%s'", Z_STRVAL_P(tmp)); - } else { - service->encoding = encoding; + // TODO Check for null bytes? + xmlCharEncodingHandlerPtr encoding = xmlFindCharEncodingHandler(Z_STRVAL_P(encoding_zv)); + if (UNEXPECTED(encoding == NULL)) { + zend_argument_value_error(2, "\"encoding\" option must be a valid encoding, \"%s\" given", Z_STRVAL_P(encoding_zv)); + goto cleanup; } + service->encoding = encoding; } - if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL && - Z_TYPE_P(tmp) == IS_ARRAY) { - if (HT_IS_PACKED(Z_ARRVAL_P(tmp))) { - php_error_docref(NULL, E_ERROR, "'classmap' option must be an associative array"); + const zval *class_map_zv = zend_hash_str_find(options, ZEND_STRL("classmap")); + if (class_map_zv) { + if (UNEXPECTED(Z_TYPE_P(class_map_zv) != IS_ARRAY)) { + zend_argument_type_error(2, "\"classmap\" option must be of type array, %s given", zend_zval_type_name(class_map_zv)); + goto cleanup; } - service->class_map = zend_array_dup(Z_ARRVAL_P(tmp)); + // TODO: this still accepts mixed keys arrays and not all numerically indexed arrays are packed + if (HT_IS_PACKED(Z_ARRVAL_P(class_map_zv))) { + zend_argument_value_error(2, "\"classmap\" option must be an associative array"); + goto cleanup; + } + service->class_map = zend_array_dup(Z_ARR_P(class_map_zv)); } - if ((tmp = zend_hash_str_find(ht, "typemap", sizeof("typemap")-1)) != NULL && - Z_TYPE_P(tmp) == IS_ARRAY && - zend_hash_num_elements(Z_ARRVAL_P(tmp)) > 0) { - typemap_ht = Z_ARRVAL_P(tmp); + const zval *type_map_zv = zend_hash_str_find(options, ZEND_STRL("typemap")); + if (type_map_zv) { + if (UNEXPECTED(Z_TYPE_P(type_map_zv) != IS_ARRAY)) { + zend_argument_type_error(2, "\"typemap\" option must be of type array, %s given", zend_zval_type_name(type_map_zv)); + goto cleanup; + } + if (zend_hash_num_elements(Z_ARR_P(type_map_zv)) > 0) { + typemap_ht = Z_ARRVAL_P(type_map_zv); + } } - if ((tmp = zend_hash_str_find(ht, "features", sizeof("features")-1)) != NULL && - Z_TYPE_P(tmp) == IS_LONG) { - service->features = Z_LVAL_P(tmp); + const zval *features_zv = zend_hash_str_find(options, ZEND_STRL("features")); + if (features_zv) { + if (UNEXPECTED(Z_TYPE_P(features_zv) != IS_LONG)) { + zend_argument_type_error(2, "\"features\" option must be of type int, %s given", zend_zval_type_name(features_zv)); + goto cleanup; + } + service->features = Z_LVAL_P(features_zv); } - if ((tmp = zend_hash_str_find(ht, "cache_wsdl", sizeof("cache_wsdl")-1)) != NULL && - Z_TYPE_P(tmp) == IS_LONG) { - cache_wsdl = Z_LVAL_P(tmp); + const zval *cache_wsdl_zv = zend_hash_str_find(options, ZEND_STRL("cache_wsdl")); + if (cache_wsdl_zv) { + if (UNEXPECTED(Z_TYPE_P(cache_wsdl_zv) != IS_LONG)) { + zend_argument_type_error(2, "\"cache_wsdl\" option must be of type int, %s given", zend_zval_type_name(cache_wsdl_zv)); + goto cleanup; + } + cache_wsdl = Z_LVAL_P(cache_wsdl_zv); } - if ((tmp = zend_hash_str_find(ht, "send_errors", sizeof("send_errors")-1)) != NULL) { - if (Z_TYPE_P(tmp) == IS_FALSE) { - service->send_errors = 0; - } else if (Z_TYPE_P(tmp) == IS_TRUE) { - service->send_errors = 1; - } else if (Z_TYPE_P(tmp) == IS_LONG) { - service->send_errors = Z_LVAL_P(tmp); + const zval *send_errors_zv = zend_hash_str_find(options, ZEND_STRL("send_errors")); + if (send_errors_zv) { + if (UNEXPECTED(Z_TYPE_P(send_errors_zv) != IS_FALSE && Z_TYPE_P(send_errors_zv) != IS_TRUE && Z_TYPE_P(send_errors_zv) != IS_LONG)) { + zend_argument_type_error(2, "\"send_errors\" option must be of type bool, %s given", zend_zval_type_name(send_errors_zv)); + goto cleanup; + } + switch (Z_TYPE_P(send_errors_zv)) { + case IS_FALSE: + service->send_errors = false; + break; + case IS_TRUE: + service->send_errors = true; + break; + case IS_LONG: + service->send_errors = Z_LVAL_P(send_errors_zv); + break; } } - if ((tmp = zend_hash_find(ht, ZSTR_KNOWN(ZEND_STR_TRACE))) != NULL && - (Z_TYPE_P(tmp) == IS_TRUE || - (Z_TYPE_P(tmp) == IS_LONG && Z_LVAL_P(tmp) == 1))) { - service->trace = true; + const zval *trace_zv = zend_hash_find(options, ZSTR_KNOWN(ZEND_STR_TRACE)); + if (trace_zv) { + if (UNEXPECTED(Z_TYPE_P(trace_zv) != IS_FALSE && Z_TYPE_P(trace_zv) != IS_TRUE && Z_TYPE_P(trace_zv) != IS_LONG)) { + zend_argument_type_error(2, "\"trace\" option must be of type bool, %s given", zend_zval_type_name(trace_zv)); + goto cleanup; + } + switch (Z_TYPE_P(trace_zv)) { + case IS_FALSE: + service->trace = false; + break; + case IS_TRUE: + service->trace = true; + break; + case IS_LONG: + service->trace = Z_LVAL_P(trace_zv); + break; + } } } else if (!wsdl) { - php_error_docref(NULL, E_ERROR, "'uri' option is required in nonWSDL mode"); + zend_argument_value_error(2, "\"uri\" option is required when argument #1 ($wsdl) is null"); + goto cleanup; } service->version = version; @@ -1037,6 +1091,7 @@ PHP_METHOD(SoapServer, __construct) service->soap_functions.functions_all = FALSE; service->soap_functions.ft = zend_new_array(0); + SOAP_SERVER_BEGIN_CODE(); if (wsdl) { zend_try { service->sdl = get_sdl(ZEND_THIS, ZSTR_VAL(wsdl), cache_wsdl); @@ -1063,6 +1118,11 @@ PHP_METHOD(SoapServer, __construct) server_obj->service = service; SOAP_SERVER_END_CODE(); + return; + +cleanup: + delete_service(service); + RETURN_THROWS(); } /* }}} */ diff --git a/ext/soap/tests/SoapServer/SoapServer_constructor_errors.phpt b/ext/soap/tests/SoapServer/SoapServer_constructor_errors.phpt new file mode 100644 index 000000000000..61b0a352b339 --- /dev/null +++ b/ext/soap/tests/SoapServer/SoapServer_constructor_errors.phpt @@ -0,0 +1,91 @@ +--TEST-- +SoapServer constructor invalid type errors +--EXTENSIONS-- +soap +--FILE-- + 25, +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'actor' => 25, +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'classmap' => 25, +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'typemap' => 25, +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'features' => 'not an int', +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'cache_wsdl' => 'not an int', +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'send_errors' => 'not an int or bool', +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +$options = [ + 'trace' => 'not an int or bool', +]; +try { + $server = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: SoapServer::__construct(): Argument #2 ($options) "uri" option must be of type string, int given +TypeError: SoapServer::__construct(): Argument #2 ($options) "actor" option must be of type string, int given +TypeError: SoapServer::__construct(): Argument #2 ($options) "classmap" option must be of type array, int given +TypeError: SoapServer::__construct(): Argument #2 ($options) "typemap" option must be of type array, int given +TypeError: SoapServer::__construct(): Argument #2 ($options) "features" option must be of type int, string given +TypeError: SoapServer::__construct(): Argument #2 ($options) "cache_wsdl" option must be of type int, string given +TypeError: SoapServer::__construct(): Argument #2 ($options) "send_errors" option must be of type bool, string given +TypeError: SoapServer::__construct(): Argument #2 ($options) "trace" option must be of type bool, string given diff --git a/ext/soap/tests/bugs/gh19784.phpt b/ext/soap/tests/SoapServer/gh19784.phpt similarity index 59% rename from ext/soap/tests/bugs/gh19784.phpt rename to ext/soap/tests/SoapServer/gh19784.phpt index 1f718e74c459..d855bc47909a 100644 --- a/ext/soap/tests/bugs/gh19784.phpt +++ b/ext/soap/tests/SoapServer/gh19784.phpt @@ -4,10 +4,10 @@ GH-19784 (SoapServer memory leak) soap --FILE-- $v_5257); -new SoapServer('foobarbaz',$v_5238,); + +$options = ['encoding' => 'UTF-8']; +$server = new SoapServer('foobarbaz', $options); + ?> --EXPECTF-- diff --git a/ext/soap/tests/SoapServer/invalid-encoding-option-type.phpt b/ext/soap/tests/SoapServer/invalid-encoding-option-type.phpt new file mode 100644 index 000000000000..c67e86c322c0 --- /dev/null +++ b/ext/soap/tests/SoapServer/invalid-encoding-option-type.phpt @@ -0,0 +1,29 @@ +--TEST-- +SoapServer constructor with an invalid encoding option +--EXTENSIONS-- +soap +--FILE-- + 25, +]; +try { + $client = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + $client = new ExtendedSoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + +?> +--EXPECT-- +TypeError: SoapServer::__construct(): Argument #2 ($options) "encoding" option must be of type string, int given +TypeError: SoapServer::__construct(): Argument #2 ($options) "encoding" option must be of type string, int given diff --git a/ext/soap/tests/SoapServer/invalid-encoding-option.phpt b/ext/soap/tests/SoapServer/invalid-encoding-option.phpt index f594d57dd8a4..08b27c43a673 100644 --- a/ext/soap/tests/SoapServer/invalid-encoding-option.phpt +++ b/ext/soap/tests/SoapServer/invalid-encoding-option.phpt @@ -25,5 +25,5 @@ try { ?> --EXPECT-- - -SOAP-ENV:ServerSoapServer::__construct(): Invalid 'encoding' option - 'non-sense' +ValueError: SoapServer::__construct(): Argument #2 ($options) "encoding" option must be a valid encoding, "non-sense" given +ValueError: SoapServer::__construct(): Argument #2 ($options) "encoding" option must be a valid encoding, "non-sense" given diff --git a/ext/soap/tests/SoapServer/invalid-soap_version-option-type.phpt b/ext/soap/tests/SoapServer/invalid-soap_version-option-type.phpt new file mode 100644 index 000000000000..690a35159a7c --- /dev/null +++ b/ext/soap/tests/SoapServer/invalid-soap_version-option-type.phpt @@ -0,0 +1,29 @@ +--TEST-- +SoapServer constructor with an invalid soap_version option +--EXTENSIONS-- +soap +--FILE-- + "I am not an integer", +]; +try { + $client = new SoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + $client = new ExtendedSoapServer($wsdl, $options); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + +?> +--EXPECT-- +TypeError: SoapServer::__construct(): Argument #2 ($options) "soap_version" option must be of type int, string given +TypeError: SoapServer::__construct(): Argument #2 ($options) "soap_version" option must be of type int, string given diff --git a/ext/soap/tests/SoapServer/invalid-soap_version-option.phpt b/ext/soap/tests/SoapServer/invalid-soap_version-option.phpt index 17e45f2dac1b..6b8b25e44e12 100644 --- a/ext/soap/tests/SoapServer/invalid-soap_version-option.phpt +++ b/ext/soap/tests/SoapServer/invalid-soap_version-option.phpt @@ -1,5 +1,5 @@ --TEST-- -SoapServer constructor with an invalid encoding option +SoapServer constructor with an invalid soap_version option --EXTENSIONS-- soap --FILE-- @@ -25,5 +25,5 @@ try { ?> --EXPECT-- - -SOAP-ENV:ServerSoapServer::__construct(): 'soap_version' option must be SOAP_1_1 or SOAP_1_2 +ValueError: SoapServer::__construct(): Argument #2 ($options) "soap_version" option must be SOAP_1_1 or SOAP_1_2 +ValueError: SoapServer::__construct(): Argument #2 ($options) "soap_version" option must be SOAP_1_1 or SOAP_1_2 diff --git a/ext/soap/tests/SoapServer/missing-options-non-wsdl-mode.phpt b/ext/soap/tests/SoapServer/missing-options-non-wsdl-mode.phpt index 54e6493879e1..4f1fc89195ed 100644 --- a/ext/soap/tests/SoapServer/missing-options-non-wsdl-mode.phpt +++ b/ext/soap/tests/SoapServer/missing-options-non-wsdl-mode.phpt @@ -48,5 +48,9 @@ try { ?> --EXPECT-- $options not provided - -SOAP-ENV:ServerSoapServer::__construct(): 'uri' option is required in nonWSDL mode +ValueError: SoapServer::__construct(): Argument #2 ($options) "uri" option is required when argument #1 ($wsdl) is null +ValueError: SoapServer::__construct(): Argument #2 ($options) "uri" option is required when argument #1 ($wsdl) is null +Empty $options array +ValueError: SoapServer::__construct(): Argument #2 ($options) "uri" option is required when argument #1 ($wsdl) is null +ValueError: SoapServer::__construct(): Argument #2 ($options) "uri" option is required when argument #1 ($wsdl) is null +$options array only sets "uri" option diff --git a/ext/soap/tests/bugs/gh16256.phpt b/ext/soap/tests/bugs/gh16256.phpt index ca8c00af5bbf..ce2ba1314069 100644 --- a/ext/soap/tests/bugs/gh16256.phpt +++ b/ext/soap/tests/bugs/gh16256.phpt @@ -21,5 +21,4 @@ try { ?> --EXPECT-- SoapClient::__construct(): 'classmap' option must be an associative array - -SOAP-ENV:ServerSoapServer::__construct(): 'classmap' option must be an associative array +SoapServer::__construct(): Argument #2 ($options) "classmap" option must be an associative array