Skip to content

Commit 10dab9e

Browse files
1 parent 5a2a150 commit 10dab9e

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
@@ -30,11 +30,12 @@ ZEND_DECLARE_MODULE_GLOBALS(filter)
3030
#include "zend_attributes.h"
3131
#include "filter_private.h"
3232
#include "filter_arginfo.h"
33+
#include "zend_exceptions.h"
3334

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

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

81+
zend_class_entry *php_filter_exception_ce;
82+
zend_class_entry *php_filter_failed_exception_ce;
83+
8084
/* {{{ filter_module_entry */
8185
zend_module_entry filter_module_entry = {
8286
STANDARD_MODULE_HEADER,
@@ -160,6 +164,9 @@ PHP_MINIT_FUNCTION(filter)
160164

161165
sapi_register_input_filter(php_sapi_filter, php_sapi_filter_init);
162166

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

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

269308
handle_default:
270309
if (options && Z_TYPE_P(options) == IS_ARRAY &&
@@ -450,7 +489,8 @@ PHP_FUNCTION(filter_has_var)
450489

451490
static void php_filter_call(
452491
zval *filtered, zend_long filter, HashTable *filter_args_ht, zend_long filter_args_long,
453-
zend_long filter_flags
492+
zend_long filter_flags,
493+
uint32_t options_arg_num
454494
) /* {{{ */ {
455495
zval *options = NULL;
456496
char *charset = NULL;
@@ -492,10 +532,28 @@ static void php_filter_call(
492532
}
493533
}
494534

535+
/* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */
536+
if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) {
537+
zval_ptr_dtor(filtered);
538+
ZVAL_NULL(filtered);
539+
zend_argument_value_error(
540+
options_arg_num,
541+
"cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE"
542+
);
543+
return;
544+
}
545+
495546
if (Z_TYPE_P(filtered) == IS_ARRAY) {
496547
if (filter_flags & FILTER_REQUIRE_SCALAR) {
497548
zval_ptr_dtor(filtered);
498-
if (filter_flags & FILTER_NULL_ON_FAILURE) {
549+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
550+
ZVAL_NULL(filtered);
551+
zend_throw_exception(
552+
php_filter_failed_exception_ce,
553+
"filter validation failed: not a scalar value (got an array)",
554+
0
555+
);
556+
} else if (filter_flags & FILTER_NULL_ON_FAILURE) {
499557
ZVAL_NULL(filtered);
500558
} else {
501559
ZVAL_FALSE(filtered);
@@ -506,6 +564,17 @@ static void php_filter_call(
506564
return;
507565
}
508566
if (filter_flags & FILTER_REQUIRE_ARRAY) {
567+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
568+
zend_throw_exception_ex(
569+
php_filter_failed_exception_ce,
570+
0,
571+
"filter validation failed: not an array (got %s)",
572+
zend_zval_value_name(filtered)
573+
);
574+
zval_ptr_dtor(filtered);
575+
ZVAL_NULL(filtered);
576+
return;
577+
}
509578
zval_ptr_dtor(filtered);
510579
if (filter_flags & FILTER_NULL_ON_FAILURE) {
511580
ZVAL_NULL(filtered);
@@ -516,6 +585,10 @@ static void php_filter_call(
516585
}
517586

518587
php_zval_filter(filtered, filter, filter_flags, options, charset);
588+
// Don't wrap in an array if we are throwing an exception
589+
if (EG(exception)) {
590+
return;
591+
}
519592
if (filter_flags & FILTER_FORCE_ARRAY) {
520593
zval tmp;
521594
ZVAL_COPY_VALUE(&tmp, filtered);
@@ -530,7 +603,7 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op
530603
) /* {{{ */ {
531604
if (!op_ht) {
532605
ZVAL_DUP(return_value, input);
533-
php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY);
606+
php_filter_call(return_value, -1, NULL, op_long, FILTER_REQUIRE_ARRAY, 2);
534607
} else {
535608
array_init(return_value);
536609
zend_string *arg_key;
@@ -557,8 +630,12 @@ static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op
557630
php_filter_call(&nval, -1,
558631
Z_TYPE_P(arg_elm) == IS_ARRAY ? Z_ARRVAL_P(arg_elm) : NULL,
559632
Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm),
560-
FILTER_REQUIRE_SCALAR
633+
FILTER_REQUIRE_SCALAR,
634+
2
561635
);
636+
if (EG(exception)) {
637+
RETURN_THROWS();
638+
}
562639
zend_hash_update(Z_ARRVAL_P(return_value), arg_key, &nval);
563640
}
564641
} ZEND_HASH_FOREACH_END();
@@ -598,11 +675,34 @@ PHP_FUNCTION(filter_input)
598675
if (!filter_args_ht) {
599676
filter_flags = filter_args_long;
600677
} else {
601-
zval *option, *opt, *def;
678+
zval *option;
602679
if ((option = zend_hash_str_find(filter_args_ht, "flags", sizeof("flags") - 1)) != NULL) {
603680
filter_flags = zval_get_long(option);
604681
}
682+
}
683+
684+
/* Cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE */
685+
if ((filter_flags & FILTER_NULL_ON_FAILURE) && (filter_flags & FILTER_THROW_ON_FAILURE)) {
686+
zend_argument_value_error(
687+
4,
688+
"cannot use both FILTER_NULL_ON_FAILURE and FILTER_THROW_ON_FAILURE"
689+
);
690+
RETURN_THROWS();
691+
}
692+
693+
if (filter_flags & FILTER_THROW_ON_FAILURE) {
694+
zend_throw_exception(
695+
php_filter_failed_exception_ce,
696+
"input value not found",
697+
0
698+
);
699+
RETURN_THROWS();
700+
}
605701

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

627727
ZVAL_DUP(return_value, tmp);
628728

629-
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR);
729+
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 4);
630730
}
631731
/* }}} */
632732

@@ -652,7 +752,7 @@ PHP_FUNCTION(filter_var)
652752

653753
ZVAL_DUP(return_value, data);
654754

655-
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR);
755+
php_filter_call(return_value, filter, filter_args_ht, filter_args_long, FILTER_REQUIRE_SCALAR, 3);
656756
}
657757
/* }}} */
658758

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)