From 86969e5ab0b428915b5803554e8dc97e99df9dd8 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 21 Jun 2025 16:10:01 -0700 Subject: [PATCH] [RFC] FILTER_THROW_ON_FAILURE https://wiki.php.net/rfc/filter_throw_on_failure --- ext/filter/callback_filter.c | 5 +- ext/filter/filter.c | 118 ++++++++++++++++-- ext/filter/filter.stub.php | 16 +++ ext/filter/filter_arginfo.h | 23 +++- ext/filter/filter_private.h | 16 ++- ext/filter/logical_filters.c | 32 +++-- ext/filter/php_filter.h | 40 +++--- ext/filter/sanitizing_filters.c | 31 +++-- .../filter_input_array_failure.phpt | 29 +++++ .../filter_input_failure.phpt | 39 ++++++ .../filter_var_array_failure.phpt | 39 ++++++ .../throw-on-failure/filter_var_failure.phpt | 37 ++++++ .../throw-and-null-error.phpt | 86 +++++++++++++ 13 files changed, 453 insertions(+), 58 deletions(-) create mode 100644 ext/filter/tests/throw-on-failure/filter_input_array_failure.phpt create mode 100644 ext/filter/tests/throw-on-failure/filter_input_failure.phpt create mode 100644 ext/filter/tests/throw-on-failure/filter_var_array_failure.phpt create mode 100644 ext/filter/tests/throw-on-failure/filter_var_failure.phpt create mode 100644 ext/filter/tests/throw-on-failure/throw-and-null-error.phpt diff --git a/ext/filter/callback_filter.c b/ext/filter/callback_filter.c index b6d57739b2b9b..719b66767980f 100644 --- a/ext/filter/callback_filter.c +++ b/ext/filter/callback_filter.c @@ -16,7 +16,7 @@ #include "php_filter.h" -void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL) { zval retval; int status; @@ -25,7 +25,7 @@ void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL) zend_type_error("%s(): Option must be a valid callback", get_active_function_name()); zval_ptr_dtor(value); ZVAL_NULL(value); - return; + return SUCCESS; } status = call_user_function(NULL, NULL, option_array, &retval, 1, value); @@ -37,4 +37,5 @@ void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL) zval_ptr_dtor(value); ZVAL_NULL(value); } + return SUCCESS; } diff --git a/ext/filter/filter.c b/ext/filter/filter.c index 50eefb440d67a..351c9c3395cb2 100644 --- a/ext/filter/filter.c +++ b/ext/filter/filter.c @@ -29,11 +29,12 @@ ZEND_DECLARE_MODULE_GLOBALS(filter) #include "filter_private.h" #include "filter_arginfo.h" +#include "zend_exceptions.h" typedef struct filter_list_entry { const char *name; int id; - void (*function)(PHP_INPUT_FILTER_PARAM_DECL); + zend_result (*function)(PHP_INPUT_FILTER_PARAM_DECL); } filter_list_entry; /* {{{ filter_list */ @@ -76,6 +77,9 @@ static const filter_list_entry filter_list[] = { static unsigned int php_sapi_filter(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len); static unsigned int php_sapi_filter_init(void); +zend_class_entry *php_filter_exception_ce; +zend_class_entry *php_filter_failed_exception_ce; + /* {{{ filter_module_entry */ zend_module_entry filter_module_entry = { STANDARD_MODULE_HEADER, @@ -159,6 +163,9 @@ PHP_MINIT_FUNCTION(filter) sapi_register_input_filter(php_sapi_filter, php_sapi_filter_init); + php_filter_exception_ce = register_class_Filter_FilterException(zend_ce_exception); + php_filter_failed_exception_ce = register_class_Filter_FilterFailedException(php_filter_exception_ce); + return SUCCESS; } /* }}} */ @@ -250,6 +257,16 @@ static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval ce = Z_OBJCE_P(value); if (!ce->__tostring) { zval_ptr_dtor(value); + if (flags & FILTER_THROW_ON_FAILURE) { + zend_throw_exception_ex( + php_filter_failed_exception_ce, + 0, + "filter validation failed: object of type %s has no __toString() method", + ZSTR_VAL(ce->name) + ); + ZVAL_NULL(value); + return; + } /* #67167: doesn't return null on failure for objects */ if (flags & FILTER_NULL_ON_FAILURE) { ZVAL_NULL(value); @@ -263,7 +280,29 @@ static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval /* Here be strings */ convert_to_string(value); - filter_func.function(value, flags, options, charset); + zend_string *copy_for_throwing = NULL; + if (flags & FILTER_THROW_ON_FAILURE) { + copy_for_throwing = zend_string_copy(Z_STR_P(value)); + } + + zend_result result = filter_func.function(value, flags, options, charset); + + if (flags & FILTER_THROW_ON_FAILURE) { + ZEND_ASSERT(copy_for_throwing != NULL); + if (result == FAILURE) { + zend_throw_exception_ex( + php_filter_failed_exception_ce, + 0, + "filter validation failed: filter %s not satisfied by %s", + filter_func.name, + ZSTR_VAL(copy_for_throwing) + ); + zend_string_delref(copy_for_throwing); + return; + } + zend_string_delref(copy_for_throwing); + copy_for_throwing = NULL; + } handle_default: if (options && Z_TYPE_P(options) == IS_ARRAY && @@ -449,7 +488,8 @@ PHP_FUNCTION(filter_has_var) static void php_filter_call( zval *filtered, zend_long filter, HashTable *filter_args_ht, zend_long filter_args_long, - zend_long filter_flags + zend_long filter_flags, + uint32_t options_arg_num ) /* {{{ */ { zval *options = NULL; char *charset = NULL; @@ -491,10 +531,28 @@ static void php_filter_call( } } + /* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */ + if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) { + zval_ptr_dtor(filtered); + ZVAL_NULL(filtered); + zend_argument_value_error( + options_arg_num, + "cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE" + ); + return; + } + if (Z_TYPE_P(filtered) == IS_ARRAY) { if (filter_flags & FILTER_REQUIRE_SCALAR) { zval_ptr_dtor(filtered); - if (filter_flags & FILTER_NULL_ON_FAILURE) { + if (filter_flags & FILTER_THROW_ON_FAILURE) { + ZVAL_NULL(filtered); + zend_throw_exception( + php_filter_failed_exception_ce, + "filter validation failed: not a scalar value (got an array)", + 0 + ); + } else if (filter_flags & FILTER_NULL_ON_FAILURE) { ZVAL_NULL(filtered); } else { ZVAL_FALSE(filtered); @@ -505,6 +563,17 @@ static void php_filter_call( return; } if (filter_flags & FILTER_REQUIRE_ARRAY) { + if (filter_flags & FILTER_THROW_ON_FAILURE) { + zend_throw_exception_ex( + php_filter_failed_exception_ce, + 0, + "filter validation failed: not an array (got %s)", + zend_zval_value_name(filtered) + ); + zval_ptr_dtor(filtered); + ZVAL_NULL(filtered); + return; + } zval_ptr_dtor(filtered); if (filter_flags & FILTER_NULL_ON_FAILURE) { ZVAL_NULL(filtered); @@ -515,6 +584,10 @@ static void php_filter_call( } php_zval_filter(filtered, filter, filter_flags, options, charset); + // Don't wrap in an array if we are throwing an exception + if (EG(exception)) { + return; + } if (filter_flags & FILTER_FORCE_ARRAY) { zval tmp; ZVAL_COPY_VALUE(&tmp, filtered); @@ -529,7 +602,7 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op ) /* {{{ */ { if (!op_ht) { ZVAL_DUP(return_value, input); - php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY); + php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY, 2); } else { array_init(return_value); zend_string *arg_key; @@ -556,8 +629,12 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op php_filter_call(&nval, -1, Z_TYPE_P(arg_elm) == IS_ARRAY ? Z_ARRVAL_P(arg_elm) : NULL, Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm), - FILTER_REQUIRE_SCALAR + FILTER_REQUIRE_SCALAR, + 2 ); + if (EG(exception)) { + RETURN_THROWS(); + } zend_hash_update(Z_ARRVAL_P(return_value), arg_key, &nval); } } ZEND_HASH_FOREACH_END(); @@ -597,11 +674,34 @@ PHP_FUNCTION(filter_input) if (!filter_args_ht) { filter_flags = filter_args_long; } else { - zval *option, *opt, *def; + zval *option; if ((option = zend_hash_str_find(filter_args_ht, "flags", sizeof("flags") - 1)) != NULL) { filter_flags = zval_get_long(option); } + } + + /* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */ + if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) { + zend_argument_value_error( + 4, + "cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE" + ); + RETURN_THROWS(); + } + + if (filter_flags & FILTER_THROW_ON_FAILURE) { + zend_throw_exception( + php_filter_failed_exception_ce, + "input value not found", + 0 + ); + RETURN_THROWS(); + } + /* FILTER_THROW_ON_FAILURE overrides defaults, needs to be checked + * before the default is used. */ + if (filter_args_ht) { + zval *opt, *def; if ((opt = zend_hash_str_find_deref(filter_args_ht, "options", sizeof("options") - 1)) != NULL && Z_TYPE_P(opt) == IS_ARRAY && (def = zend_hash_str_find_deref(Z_ARRVAL_P(opt), "default", sizeof("default") - 1)) != NULL @@ -625,7 +725,7 @@ PHP_FUNCTION(filter_input) ZVAL_DUP(return_value, tmp); - php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR); + php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 4); } /* }}} */ @@ -651,7 +751,7 @@ PHP_FUNCTION(filter_var) ZVAL_DUP(return_value, data); - php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR); + php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 3); } /* }}} */ diff --git a/ext/filter/filter.stub.php b/ext/filter/filter.stub.php index 030de50f51890..e2667ccbb6588 100644 --- a/ext/filter/filter.stub.php +++ b/ext/filter/filter.stub.php @@ -2,6 +2,7 @@ /** @generate-class-entries */ +namespace { /** * @var int * @cvalue PARSE_POST @@ -54,6 +55,11 @@ * @cvalue FILTER_NULL_ON_FAILURE */ const FILTER_NULL_ON_FAILURE = UNKNOWN; +/** + * @var int + * @cvalue FILTER_THROW_ON_FAILURE + */ +const FILTER_THROW_ON_FAILURE = UNKNOWN; /** * @var int @@ -313,3 +319,13 @@ function filter_var_array(array $array, array|int $options = FILTER_DEFAULT, boo function filter_list(): array {} function filter_id(string $name): int|false {} + +} + +namespace Filter { + + class FilterException extends \Exception {} + + class FilterFailedException extends FilterException {} + +} diff --git a/ext/filter/filter_arginfo.h b/ext/filter/filter_arginfo.h index a05806c5e1201..48e743775c93a 100644 --- a/ext/filter/filter_arginfo.h +++ b/ext/filter/filter_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c3f3240137eaa89316276920acf35f975b2dd8f9 */ + * Stub hash: 5ba9c0edca2712b1c3d37e19eec722d0921e72a1 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_filter_has_var, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, input_type, IS_LONG, 0) @@ -69,6 +69,7 @@ static void register_filter_symbols(int module_number) REGISTER_LONG_CONSTANT("FILTER_REQUIRE_ARRAY", FILTER_REQUIRE_ARRAY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_FORCE_ARRAY", FILTER_FORCE_ARRAY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_NULL_ON_FAILURE", FILTER_NULL_ON_FAILURE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FILTER_THROW_ON_FAILURE", FILTER_THROW_ON_FAILURE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_VALIDATE_INT", FILTER_VALIDATE_INT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_VALIDATE_BOOLEAN", FILTER_VALIDATE_BOOL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_VALIDATE_BOOL", FILTER_VALIDATE_BOOL, CONST_PERSISTENT); @@ -115,3 +116,23 @@ static void register_filter_symbols(int module_number) REGISTER_LONG_CONSTANT("FILTER_FLAG_HOSTNAME", FILTER_FLAG_HOSTNAME, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("FILTER_FLAG_EMAIL_UNICODE", FILTER_FLAG_EMAIL_UNICODE, CONST_PERSISTENT); } + +static zend_class_entry *register_class_Filter_FilterException(zend_class_entry *class_entry_Exception) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Filter", "FilterException", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Exception, 0); + + return class_entry; +} + +static zend_class_entry *register_class_Filter_FilterFailedException(zend_class_entry *class_entry_Filter_FilterException) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Filter", "FilterFailedException", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Filter_FilterException, 0); + + return class_entry; +} diff --git a/ext/filter/filter_private.h b/ext/filter/filter_private.h index a1bbb500f8db4..709b7fbc45edc 100644 --- a/ext/filter/filter_private.h +++ b/ext/filter/filter_private.h @@ -24,6 +24,7 @@ #define FILTER_FORCE_ARRAY 0x4000000 #define FILTER_NULL_ON_FAILURE 0x8000000 +#define FILTER_THROW_ON_FAILURE 0x10000000 #define FILTER_FLAG_ALLOW_OCTAL 0x0001 #define FILTER_FLAG_ALLOW_HEX 0x0002 @@ -50,7 +51,7 @@ #define FILTER_FLAG_IPV6 0x00200000 #define FILTER_FLAG_NO_RES_RANGE 0x00400000 #define FILTER_FLAG_NO_PRIV_RANGE 0x00800000 -#define FILTER_FLAG_GLOBAL_RANGE 0x10000000 +#define FILTER_FLAG_GLOBAL_RANGE 0x20000000 #define FILTER_FLAG_HOSTNAME 0x100000 @@ -93,9 +94,18 @@ || (id >= FILTER_VALIDATE_ALL && id <= FILTER_VALIDATE_LAST) \ || id == FILTER_CALLBACK) + +/* When using FILTER_THROW_ON_FAILURE, we can't actually throw the error here + * because we don't have access to the name of the filter. Returning FAILURE + * from the filter handler indicates that validation failed *and* an exception + * should thus be thrown. */ #define RETURN_VALIDATION_FAILED \ if (EG(exception)) { \ - return; \ + return SUCCESS; \ + } else if (flags & FILTER_THROW_ON_FAILURE) { \ + zval_ptr_dtor(value); \ + ZVAL_NULL(value); \ + return FAILURE; \ } else if (flags & FILTER_NULL_ON_FAILURE) { \ zval_ptr_dtor(value); \ ZVAL_NULL(value); \ @@ -103,7 +113,7 @@ zval_ptr_dtor(value); \ ZVAL_FALSE(value); \ } \ - return; \ + return SUCCESS; \ #define PHP_FILTER_TRIM_DEFAULT(p, len) PHP_FILTER_TRIM_DEFAULT_EX(p, len, 1); diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 0dd307122345c..730586f08890c 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -195,7 +195,7 @@ static bool php_filter_parse_hex(const char *str, size_t str_len, zend_long *ret } /* }}} */ -void php_filter_int(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_int(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { zval *option_val; zend_long min_range, max_range, option_flags; @@ -266,12 +266,12 @@ void php_filter_int(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } else { zval_ptr_dtor(value); ZVAL_LONG(value, ctx_value); - return; } + return SUCCESS; } /* }}} */ -void php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { const char *str = Z_STRVAL_P(value); size_t len = Z_STRLEN_P(value); @@ -337,10 +337,11 @@ void php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ zval_ptr_dtor(value); ZVAL_BOOL(value, ret); } + return SUCCESS; } /* }}} */ -void php_filter_float(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_float(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { size_t len; const char *str, *end; @@ -467,10 +468,11 @@ void php_filter_float(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ RETURN_VALIDATION_FAILED } efree(num); + return SUCCESS; } /* }}} */ -void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { zval *option_val; zend_string *regexp; @@ -503,6 +505,7 @@ void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ if (rc < 0) { RETURN_VALIDATION_FAILED } + return SUCCESS; } static bool php_filter_validate_domain_ex(const zend_string *domain, zend_long flags) /* {{{ */ @@ -557,11 +560,12 @@ static bool php_filter_validate_domain_ex(const zend_string *domain, zend_long f } /* }}} */ -void php_filter_validate_domain(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_domain(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { if (!php_filter_validate_domain_ex(Z_STR_P(value), flags)) { RETURN_VALIDATION_FAILED } + return SUCCESS; } /* }}} */ @@ -589,7 +593,7 @@ static bool php_filter_is_valid_ipv6_hostname(const zend_string *s) return *ZSTR_VAL(s) == '[' && *t == ']' && _php_filter_validate_ipv6(ZSTR_VAL(s) + 1, ZSTR_LEN(s) - 2, NULL); } -void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { php_url *url; size_t old_len = Z_STRLEN_P(value); @@ -646,10 +650,11 @@ void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } php_url_free(url); + return SUCCESS; } /* }}} */ -void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { /* * The regex below is based on a regex by Michael Rushton. @@ -715,6 +720,7 @@ void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ if (rc < 0) { RETURN_VALIDATION_FAILED } + return SUCCESS; } /* }}} */ @@ -975,7 +981,7 @@ static bool ipv6_get_status_flags(const int ip[8], bool *global, bool *reserved, * to throw out reserved ranges; multicast ranges... etc. If both allow_ipv4 * and allow_ipv6 flags flag are used, then the first dot or colon determine * the format */ -void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { int ip[8]; int mode; @@ -1003,7 +1009,7 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } if (!ipv4_get_status_flags(ip, &flag_global, &flag_reserved, &flag_private)) { - return; /* no special block */ + return SUCCESS; /* no special block */ } } else if (mode == FORMAT_IPV6) { @@ -1012,7 +1018,7 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ } if (!ipv6_get_status_flags(ip, &flag_global, &flag_reserved, &flag_private)) { - return; /* no special block */ + return SUCCESS; /* no special block */ } } @@ -1027,10 +1033,11 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ if ((flags & FILTER_FLAG_NO_RES_RANGE) && flag_reserved == true) { RETURN_VALIDATION_FAILED } + return SUCCESS; } /* }}} */ -void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ +zend_result php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { const char *input = Z_STRVAL_P(value); size_t input_len = Z_STRLEN_P(value); @@ -1089,5 +1096,6 @@ void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ RETURN_VALIDATION_FAILED } } + return SUCCESS; } /* }}} */ diff --git a/ext/filter/php_filter.h b/ext/filter/php_filter.h index f782907898fca..48ad5cc07943e 100644 --- a/ext/filter/php_filter.h +++ b/ext/filter/php_filter.h @@ -53,27 +53,27 @@ ZEND_TSRMLS_CACHE_EXTERN() #define IF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(filter, v) #define PHP_INPUT_FILTER_PARAM_DECL zval *value, zend_long flags, zval *option_array, char *charset -void php_filter_int(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_float(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_domain(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_int(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_float(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_domain(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_string(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_encoded(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_full_special_chars(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_email(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_url(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_number_int(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_number_float(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_add_slashes(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_string(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_encoded(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_full_special_chars(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_email(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_url(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_number_int(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_number_float(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_add_slashes(PHP_INPUT_FILTER_PARAM_DECL); -void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL); +zend_result php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL); #endif /* FILTER_H */ diff --git a/ext/filter/sanitizing_filters.c b/ext/filter/sanitizing_filters.c index ebc20e47711db..94cbd0c34bba7 100644 --- a/ext/filter/sanitizing_filters.c +++ b/ext/filter/sanitizing_filters.c @@ -168,7 +168,7 @@ static void filter_map_apply(zval *value, const filter_map *map) /* }}} */ /* {{{ php_filter_string */ -void php_filter_string(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_string(PHP_INPUT_FILTER_PARAM_DECL) { size_t new_len; unsigned char enc[256] = {0}; @@ -206,23 +206,24 @@ void php_filter_string(PHP_INPUT_FILTER_PARAM_DECL) } else { ZVAL_EMPTY_STRING(value); } - return; } + return SUCCESS; } /* }}} */ /* {{{ php_filter_encoded */ -void php_filter_encoded(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_encoded(PHP_INPUT_FILTER_PARAM_DECL) { /* apply strip_high and strip_low filters */ php_filter_strip(value, flags); /* urlencode */ php_filter_encode_url(value, (unsigned char *)DEFAULT_URL_ENCODE, sizeof(DEFAULT_URL_ENCODE)-1); + return SUCCESS; } /* }}} */ /* {{{ php_filter_special_chars */ -void php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL) { unsigned char enc[256] = {0}; @@ -239,11 +240,12 @@ void php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL) } php_filter_encode_html(value, enc); + return SUCCESS; } /* }}} */ /* {{{ php_filter_full_special_chars */ -void php_filter_full_special_chars(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_full_special_chars(PHP_INPUT_FILTER_PARAM_DECL) { zend_string *buf; int quotes; @@ -258,11 +260,12 @@ void php_filter_full_special_chars(PHP_INPUT_FILTER_PARAM_DECL) /* charset_hint */ NULL, /* double_encode */ 0, /* quiet */ 0); zval_ptr_dtor(value); ZVAL_STR(value, buf); + return SUCCESS; } /* }}} */ /* {{{ php_filter_unsafe_raw */ -void php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL) { /* Only if no flags are set (optimization) */ if (flags != 0 && Z_STRLEN_P(value) > 0) { @@ -285,6 +288,7 @@ void php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL) zval_ptr_dtor(value); ZVAL_NULL(value); } + return SUCCESS; } /* }}} */ @@ -295,7 +299,7 @@ void php_filter_unsafe_raw(PHP_INPUT_FILTER_PARAM_DECL) #define PUNCTUATION "<>#%\"" #define RESERVED ";/?:@&=" -void php_filter_email(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_email(PHP_INPUT_FILTER_PARAM_DECL) { /* Check section 6 of rfc 822 http://www.faqs.org/rfcs/rfc822.html */ const unsigned char allowed_list[] = LOWALPHA HIALPHA DIGIT "!#$%&'*+-=?^_`{|}~@.[]"; @@ -304,11 +308,12 @@ void php_filter_email(PHP_INPUT_FILTER_PARAM_DECL) filter_map_init(&map); filter_map_update(&map, 1, allowed_list); filter_map_apply(value, &map); + return SUCCESS; } /* }}} */ /* {{{ php_filter_url */ -void php_filter_url(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_url(PHP_INPUT_FILTER_PARAM_DECL) { /* Strip all chars not part of section 5 of * http://www.faqs.org/rfcs/rfc1738.html */ @@ -318,11 +323,12 @@ void php_filter_url(PHP_INPUT_FILTER_PARAM_DECL) filter_map_init(&map); filter_map_update(&map, 1, allowed_list); filter_map_apply(value, &map); + return SUCCESS; } /* }}} */ /* {{{ php_filter_number_int */ -void php_filter_number_int(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_number_int(PHP_INPUT_FILTER_PARAM_DECL) { /* strip everything [^0-9+-] */ const unsigned char allowed_list[] = "+-" DIGIT; @@ -331,11 +337,12 @@ void php_filter_number_int(PHP_INPUT_FILTER_PARAM_DECL) filter_map_init(&map); filter_map_update(&map, 1, allowed_list); filter_map_apply(value, &map); + return SUCCESS; } /* }}} */ /* {{{ php_filter_number_float */ -void php_filter_number_float(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_number_float(PHP_INPUT_FILTER_PARAM_DECL) { /* strip everything [^0-9+-] */ const unsigned char allowed_list[] = "+-" DIGIT; @@ -355,15 +362,17 @@ void php_filter_number_float(PHP_INPUT_FILTER_PARAM_DECL) filter_map_update(&map, 4, (const unsigned char *) "eE"); } filter_map_apply(value, &map); + return SUCCESS; } /* }}} */ /* {{{ php_filter_add_slashes */ -void php_filter_add_slashes(PHP_INPUT_FILTER_PARAM_DECL) +zend_result php_filter_add_slashes(PHP_INPUT_FILTER_PARAM_DECL) { zend_string *buf = php_addslashes(Z_STR_P(value)); zval_ptr_dtor(value); ZVAL_STR(value, buf); + return SUCCESS; } /* }}} */ diff --git a/ext/filter/tests/throw-on-failure/filter_input_array_failure.phpt b/ext/filter/tests/throw-on-failure/filter_input_array_failure.phpt new file mode 100644 index 0000000000000..c9f128afbb9f1 --- /dev/null +++ b/ext/filter/tests/throw-on-failure/filter_input_array_failure.phpt @@ -0,0 +1,29 @@ +--TEST-- +FILTER_THROW_ON_FAILURE: filter_input_array() failure +--EXTENSIONS-- +filter +--GET-- +a=1 +--FILE-- + ['flags' => FILTER_REQUIRE_ARRAY | FILTER_THROW_ON_FAILURE]]); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nvalidation fails (filter value)\n"; +try { + filter_input_array(INPUT_GET, ['a' => ['filter' => FILTER_VALIDATE_EMAIL, 'flags' => FILTER_THROW_ON_FAILURE]]); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +validation fails (array type check) +Filter\FilterFailedException: filter validation failed: not an array (got string) + +validation fails (filter value) +Filter\FilterFailedException: filter validation failed: filter validate_email not satisfied by 1 diff --git a/ext/filter/tests/throw-on-failure/filter_input_failure.phpt b/ext/filter/tests/throw-on-failure/filter_input_failure.phpt new file mode 100644 index 0000000000000..5323d17b7e54e --- /dev/null +++ b/ext/filter/tests/throw-on-failure/filter_input_failure.phpt @@ -0,0 +1,39 @@ +--TEST-- +FILTER_THROW_ON_FAILURE: filter_input() failure +--EXTENSIONS-- +filter +--GET-- +a=1 +--FILE-- +getMessage() . "\n"; +} + +echo "\nvalidation fails (array type check)\n"; +try { + filter_input(INPUT_GET, 'a', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY | FILTER_THROW_ON_FAILURE); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nvalidation fails (filter value)\n"; +try { + filter_input(INPUT_GET, 'a', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +missing value +Filter\FilterFailedException: input value not found + +validation fails (array type check) +Filter\FilterFailedException: filter validation failed: not an array (got string) + +validation fails (filter value) +Filter\FilterFailedException: filter validation failed: filter validate_email not satisfied by 1 diff --git a/ext/filter/tests/throw-on-failure/filter_var_array_failure.phpt b/ext/filter/tests/throw-on-failure/filter_var_array_failure.phpt new file mode 100644 index 0000000000000..0b57d406490b9 --- /dev/null +++ b/ext/filter/tests/throw-on-failure/filter_var_array_failure.phpt @@ -0,0 +1,39 @@ +--TEST-- +FILTER_THROW_ON_FAILURE: filter_var_array() failure +--EXTENSIONS-- +filter +--GET-- +a=1 +--FILE-- + 'a'], ['a' => ['flags' => FILTER_REQUIRE_ARRAY | FILTER_THROW_ON_FAILURE]]); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nvalidation fails (object without __toString)\n"; +try { + filter_var_array(['a' => new stdClass()], ['a' => ['flags' => FILTER_THROW_ON_FAILURE]]); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nvalidation fails (filter value)\n"; +try { + filter_var_array(['a' => true], ['a' => ['filter' => FILTER_VALIDATE_EMAIL, 'flags' => FILTER_THROW_ON_FAILURE]]); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +validation fails (array type check) +Filter\FilterFailedException: filter validation failed: not an array (got string) + +validation fails (object without __toString) +Filter\FilterFailedException: filter validation failed: object of type stdClass has no __toString() method + +validation fails (filter value) +Filter\FilterFailedException: filter validation failed: filter validate_email not satisfied by 1 diff --git a/ext/filter/tests/throw-on-failure/filter_var_failure.phpt b/ext/filter/tests/throw-on-failure/filter_var_failure.phpt new file mode 100644 index 0000000000000..9ac6609499fda --- /dev/null +++ b/ext/filter/tests/throw-on-failure/filter_var_failure.phpt @@ -0,0 +1,37 @@ +--TEST-- +FILTER_THROW_ON_FAILURE: filter_input() failure +--EXTENSIONS-- +filter +--FILE-- +getMessage() . "\n"; +} + +echo "\nvalidation fails (object without __toString)\n"; +try { + filter_var(new stdClass(), FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nvalidation fails (filter value)\n"; +try { + filter_var('a', FILTER_VALIDATE_EMAIL, FILTER_THROW_ON_FAILURE); +} catch (Filter\FilterFailedException $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +validation fails (array type check) +Filter\FilterFailedException: filter validation failed: not an array (got string) + +validation fails (object without __toString) +Filter\FilterFailedException: filter validation failed: object of type stdClass has no __toString() method + +validation fails (filter value) +Filter\FilterFailedException: filter validation failed: filter validate_email not satisfied by a diff --git a/ext/filter/tests/throw-on-failure/throw-and-null-error.phpt b/ext/filter/tests/throw-on-failure/throw-and-null-error.phpt new file mode 100644 index 0000000000000..48c37618ed917 --- /dev/null +++ b/ext/filter/tests/throw-on-failure/throw-and-null-error.phpt @@ -0,0 +1,86 @@ +--TEST-- +Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE +--EXTENSIONS-- +filter +--GET-- +a=1 +--FILE-- +getMessage() . "\n"; +} +try { + filter_input(INPUT_GET, 'b', FILTER_DEFAULT, ['flags' => $flags]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +echo "\nfilter_input(), with a missing value and a default\n"; +try { + filter_input(INPUT_GET, 'b', FILTER_DEFAULT, ['flags' => $flags, 'options' => ['default' => 'a']]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +echo "\nfilter_input(), with a present value\n"; +try { + filter_input(INPUT_GET, 'a', FILTER_DEFAULT, $flags); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +try { + filter_input(INPUT_GET, 'a', FILTER_DEFAULT, ['flags' => $flags]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +echo "\nfilter_var()\n"; +try { + filter_var(true, FILTER_DEFAULT, $flags); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +try { + filter_var(true, FILTER_DEFAULT, ['flags' => $flags]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nfilter_input_array()\n"; +try { + filter_input_array(INPUT_GET, ['a' => ['flags' => $flags]]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} + +echo "\nfilter_var_array()\n"; +try { + filter_var_array(['a' => true], ['a' => ['flags' => $flags]]); +} catch (ValueError $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +filter_input(), with a missing value +ValueError: filter_input(): Argument #4 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE +ValueError: filter_input(): Argument #4 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE + +filter_input(), with a missing value and a default +ValueError: filter_input(): Argument #4 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE + +filter_input(), with a present value +ValueError: filter_input(): Argument #4 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE +ValueError: filter_input(): Argument #4 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE + +filter_var() +ValueError: filter_var(): Argument #3 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE +ValueError: filter_var(): Argument #3 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE + +filter_input_array() +ValueError: filter_input_array(): Argument #2 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE + +filter_var_array() +ValueError: filter_var_array(): Argument #2 ($options) cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE