Skip to content

Commit 86969e5

Browse files
1 parent 27e485d commit 86969e5

13 files changed

+453
-58
lines changed

ext/filter/callback_filter.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
#include "php_filter.h"
1818

19-
void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL)
19+
zend_result php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL)
2020
{
2121
zval retval;
2222
int status;
@@ -25,7 +25,7 @@ void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL)
2525
zend_type_error("%s(): Option must be a valid callback", get_active_function_name());
2626
zval_ptr_dtor(value);
2727
ZVAL_NULL(value);
28-
return;
28+
return SUCCESS;
2929
}
3030

3131
status = call_user_function(NULL, NULL, option_array, &retval, 1, value);
@@ -37,4 +37,5 @@ void php_filter_callback(PHP_INPUT_FILTER_PARAM_DECL)
3737
zval_ptr_dtor(value);
3838
ZVAL_NULL(value);
3939
}
40+
return SUCCESS;
4041
}

ext/filter/filter.c

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ ZEND_DECLARE_MODULE_GLOBALS(filter)
2929

3030
#include "filter_private.h"
3131
#include "filter_arginfo.h"
32+
#include "zend_exceptions.h"
3233

3334
typedef struct filter_list_entry {
3435
const char *name;
3536
int id;
36-
void (*function)(PHP_INPUT_FILTER_PARAM_DECL);
37+
zend_result (*function)(PHP_INPUT_FILTER_PARAM_DECL);
3738
} filter_list_entry;
3839

3940
/* {{{ filter_list */
@@ -76,6 +77,9 @@ static const filter_list_entry filter_list[] = {
7677
static unsigned int php_sapi_filter(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len);
7778
static unsigned int php_sapi_filter_init(void);
7879

80+
zend_class_entry *php_filter_exception_ce;
81+
zend_class_entry *php_filter_failed_exception_ce;
82+
7983
/* {{{ filter_module_entry */
8084
zend_module_entry filter_module_entry = {
8185
STANDARD_MODULE_HEADER,
@@ -159,6 +163,9 @@ PHP_MINIT_FUNCTION(filter)
159163

160164
sapi_register_input_filter(php_sapi_filter, php_sapi_filter_init);
161165

166+
php_filter_exception_ce = register_class_Filter_FilterException(zend_ce_exception);
167+
php_filter_failed_exception_ce = register_class_Filter_FilterFailedException(php_filter_exception_ce);
168+
162169
return SUCCESS;
163170
}
164171
/* }}} */
@@ -250,6 +257,16 @@ static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval
250257
ce = Z_OBJCE_P(value);
251258
if (!ce->__tostring) {
252259
zval_ptr_dtor(value);
260+
if (flags & FILTER_THROW_ON_FAILURE) {
261+
zend_throw_exception_ex(
262+
php_filter_failed_exception_ce,
263+
0,
264+
"filter validation failed: object of type %s has no __toString() method",
265+
ZSTR_VAL(ce->name)
266+
);
267+
ZVAL_NULL(value);
268+
return;
269+
}
253270
/* #67167: doesn't return null on failure for objects */
254271
if (flags & FILTER_NULL_ON_FAILURE) {
255272
ZVAL_NULL(value);
@@ -263,7 +280,29 @@ static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval
263280
/* Here be strings */
264281
convert_to_string(value);
265282

266-
filter_func.function(value, flags, options, charset);
283+
zend_string *copy_for_throwing = NULL;
284+
if (flags & FILTER_THROW_ON_FAILURE) {
285+
copy_for_throwing = zend_string_copy(Z_STR_P(value));
286+
}
287+
288+
zend_result result = filter_func.function(value, flags, options, charset);
289+
290+
if (flags & FILTER_THROW_ON_FAILURE) {
291+
ZEND_ASSERT(copy_for_throwing != NULL);
292+
if (result == FAILURE) {
293+
zend_throw_exception_ex(
294+
php_filter_failed_exception_ce,
295+
0,
296+
"filter validation failed: filter %s not satisfied by %s",
297+
filter_func.name,
298+
ZSTR_VAL(copy_for_throwing)
299+
);
300+
zend_string_delref(copy_for_throwing);
301+
return;
302+
}
303+
zend_string_delref(copy_for_throwing);
304+
copy_for_throwing = NULL;
305+
}
267306

268307
handle_default:
269308
if (options && Z_TYPE_P(options) == IS_ARRAY &&
@@ -449,7 +488,8 @@ PHP_FUNCTION(filter_has_var)
449488

450489
static void php_filter_call(
451490
zval *filtered, zend_long filter, HashTable *filter_args_ht, zend_long filter_args_long,
452-
zend_long filter_flags
491+
zend_long filter_flags,
492+
uint32_t options_arg_num
453493
) /* {{{ */ {
454494
zval *options = NULL;
455495
char *charset = NULL;
@@ -491,10 +531,28 @@ static void php_filter_call(
491531
}
492532
}
493533

534+
/* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */
535+
if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) {
536+
zval_ptr_dtor(filtered);
537+
ZVAL_NULL(filtered);
538+
zend_argument_value_error(
539+
options_arg_num,
540+
"cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE"
541+
);
542+
return;
543+
}
544+
494545
if (Z_TYPE_P(filtered) == IS_ARRAY) {
495546
if (filter_flags & FILTER_REQUIRE_SCALAR) {
496547
zval_ptr_dtor(filtered);
497-
if (filter_flags & FILTER_NULL_ON_FAILURE) {
548+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
549+
ZVAL_NULL(filtered);
550+
zend_throw_exception(
551+
php_filter_failed_exception_ce,
552+
"filter validation failed: not a scalar value (got an array)",
553+
0
554+
);
555+
} else if (filter_flags & FILTER_NULL_ON_FAILURE) {
498556
ZVAL_NULL(filtered);
499557
} else {
500558
ZVAL_FALSE(filtered);
@@ -505,6 +563,17 @@ static void php_filter_call(
505563
return;
506564
}
507565
if (filter_flags & FILTER_REQUIRE_ARRAY) {
566+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
567+
zend_throw_exception_ex(
568+
php_filter_failed_exception_ce,
569+
0,
570+
"filter validation failed: not an array (got %s)",
571+
zend_zval_value_name(filtered)
572+
);
573+
zval_ptr_dtor(filtered);
574+
ZVAL_NULL(filtered);
575+
return;
576+
}
508577
zval_ptr_dtor(filtered);
509578
if (filter_flags & FILTER_NULL_ON_FAILURE) {
510579
ZVAL_NULL(filtered);
@@ -515,6 +584,10 @@ static void php_filter_call(
515584
}
516585

517586
php_zval_filter(filtered, filter, filter_flags, options, charset);
587+
// Don't wrap in an array if we are throwing an exception
588+
if (EG(exception)) {
589+
return;
590+
}
518591
if (filter_flags & FILTER_FORCE_ARRAY) {
519592
zval tmp;
520593
ZVAL_COPY_VALUE(&tmp, filtered);
@@ -529,7 +602,7 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op
529602
) /* {{{ */ {
530603
if (!op_ht) {
531604
ZVAL_DUP(return_value, input);
532-
php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY);
605+
php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY, 2);
533606
} else {
534607
array_init(return_value);
535608
zend_string *arg_key;
@@ -556,8 +629,12 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op
556629
php_filter_call(&nval, -1,
557630
Z_TYPE_P(arg_elm) == IS_ARRAY ? Z_ARRVAL_P(arg_elm) : NULL,
558631
Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm),
559-
FILTER_REQUIRE_SCALAR
632+
FILTER_REQUIRE_SCALAR,
633+
2
560634
);
635+
if (EG(exception)) {
636+
RETURN_THROWS();
637+
}
561638
zend_hash_update(Z_ARRVAL_P(return_value), arg_key, &nval);
562639
}
563640
} ZEND_HASH_FOREACH_END();
@@ -597,11 +674,34 @@ PHP_FUNCTION(filter_input)
597674
if (!filter_args_ht) {
598675
filter_flags = filter_args_long;
599676
} else {
600-
zval *option, *opt, *def;
677+
zval *option;
601678
if ((option = zend_hash_str_find(filter_args_ht, "flags", sizeof("flags") - 1)) != NULL) {
602679
filter_flags = zval_get_long(option);
603680
}
681+
}
682+
683+
/* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */
684+
if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) {
685+
zend_argument_value_error(
686+
4,
687+
"cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE"
688+
);
689+
RETURN_THROWS();
690+
}
691+
692+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
693+
zend_throw_exception(
694+
php_filter_failed_exception_ce,
695+
"input value not found",
696+
0
697+
);
698+
RETURN_THROWS();
699+
}
604700

701+
/* FILTER_THROW_ON_FAILURE overrides defaults, needs to be checked
702+
* before the default is used. */
703+
if (filter_args_ht) {
704+
zval *opt, *def;
605705
if ((opt = zend_hash_str_find_deref(filter_args_ht, "options", sizeof("options") - 1)) != NULL &&
606706
Z_TYPE_P(opt) == IS_ARRAY &&
607707
(def = zend_hash_str_find_deref(Z_ARRVAL_P(opt), "default", sizeof("default") - 1)) != NULL
@@ -625,7 +725,7 @@ PHP_FUNCTION(filter_input)
625725

626726
ZVAL_DUP(return_value, tmp);
627727

628-
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR);
728+
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 4);
629729
}
630730
/* }}} */
631731

@@ -651,7 +751,7 @@ PHP_FUNCTION(filter_var)
651751

652752
ZVAL_DUP(return_value, data);
653753

654-
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR);
754+
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 3);
655755
}
656756
/* }}} */
657757

ext/filter/filter.stub.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
/** @generate-class-entries */
44

5+
namespace {
56
/**
67
* @var int
78
* @cvalue PARSE_POST
@@ -54,6 +55,11 @@
5455
* @cvalue FILTER_NULL_ON_FAILURE
5556
*/
5657
const FILTER_NULL_ON_FAILURE = UNKNOWN;
58+
/**
59+
* @var int
60+
* @cvalue FILTER_THROW_ON_FAILURE
61+
*/
62+
const FILTER_THROW_ON_FAILURE = UNKNOWN;
5763

5864
/**
5965
* @var int
@@ -313,3 +319,13 @@ function filter_var_array(array $array, array|int $options = FILTER_DEFAULT, boo
313319
function filter_list(): array {}
314320

315321
function filter_id(string $name): int|false {}
322+
323+
}
324+
325+
namespace Filter {
326+
327+
class FilterException extends \Exception {}
328+
329+
class FilterFailedException extends FilterException {}
330+
331+
}

ext/filter/filter_arginfo.h

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/filter/filter_private.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#define FILTER_FORCE_ARRAY 0x4000000
2626
#define FILTER_NULL_ON_FAILURE 0x8000000
27+
#define FILTER_THROW_ON_FAILURE 0x10000000
2728

2829
#define FILTER_FLAG_ALLOW_OCTAL 0x0001
2930
#define FILTER_FLAG_ALLOW_HEX 0x0002
@@ -50,7 +51,7 @@
5051
#define FILTER_FLAG_IPV6 0x00200000
5152
#define FILTER_FLAG_NO_RES_RANGE 0x00400000
5253
#define FILTER_FLAG_NO_PRIV_RANGE 0x00800000
53-
#define FILTER_FLAG_GLOBAL_RANGE 0x10000000
54+
#define FILTER_FLAG_GLOBAL_RANGE 0x20000000
5455

5556
#define FILTER_FLAG_HOSTNAME 0x100000
5657

@@ -93,17 +94,26 @@
9394
|| (id >= FILTER_VALIDATE_ALL && id <= FILTER_VALIDATE_LAST) \
9495
|| id == FILTER_CALLBACK)
9596

97+
98+
/* When using FILTER_THROW_ON_FAILURE, we can't actually throw the error here
99+
* because we don't have access to the name of the filter. Returning FAILURE
100+
* from the filter handler indicates that validation failed *and* an exception
101+
* should thus be thrown. */
96102
#define RETURN_VALIDATION_FAILED \
97103
if (EG(exception)) { \
98-
return; \
104+
return SUCCESS; \
105+
} else if (flags & FILTER_THROW_ON_FAILURE) { \
106+
zval_ptr_dtor(value); \
107+
ZVAL_NULL(value); \
108+
return FAILURE; \
99109
} else if (flags & FILTER_NULL_ON_FAILURE) { \
100110
zval_ptr_dtor(value); \
101111
ZVAL_NULL(value); \
102112
} else { \
103113
zval_ptr_dtor(value); \
104114
ZVAL_FALSE(value); \
105115
} \
106-
return; \
116+
return SUCCESS; \
107117

108118
#define PHP_FILTER_TRIM_DEFAULT(p, len) PHP_FILTER_TRIM_DEFAULT_EX(p, len, 1);
109119

0 commit comments

Comments
 (0)