xref: /PHP-8.1/ext/filter/filter.c (revision c2fb10d2)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
14   |          Derick Rethans <derick@php.net>                             |
15   |          Pierre-A. Joye <pierre@php.net>                             |
16   |          Ilia Alshanetsky <iliaa@php.net>                            |
17   +----------------------------------------------------------------------+
18 */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "php_filter.h"
25 
26 ZEND_DECLARE_MODULE_GLOBALS(filter)
27 
28 #include "filter_private.h"
29 #include "filter_arginfo.h"
30 
31 typedef struct filter_list_entry {
32 	const char *name;
33 	int    id;
34 	void (*function)(PHP_INPUT_FILTER_PARAM_DECL);
35 } filter_list_entry;
36 
37 /* {{{ filter_list */
38 static const filter_list_entry filter_list[] = {
39 	{ "int",             FILTER_VALIDATE_INT,           php_filter_int             },
40 	{ "boolean",         FILTER_VALIDATE_BOOL,          php_filter_boolean         },
41 	{ "float",           FILTER_VALIDATE_FLOAT,         php_filter_float           },
42 
43 	{ "validate_regexp", FILTER_VALIDATE_REGEXP,        php_filter_validate_regexp },
44 	{ "validate_domain", FILTER_VALIDATE_DOMAIN,        php_filter_validate_domain },
45 	{ "validate_url",    FILTER_VALIDATE_URL,           php_filter_validate_url    },
46 	{ "validate_email",  FILTER_VALIDATE_EMAIL,         php_filter_validate_email  },
47 	{ "validate_ip",     FILTER_VALIDATE_IP,            php_filter_validate_ip     },
48 	{ "validate_mac",    FILTER_VALIDATE_MAC,           php_filter_validate_mac    },
49 
50 	{ "string",          FILTER_SANITIZE_STRING,        php_filter_string          },
51 	{ "stripped",        FILTER_SANITIZE_STRING,        php_filter_string          },
52 	{ "encoded",         FILTER_SANITIZE_ENCODED,       php_filter_encoded         },
53 	{ "special_chars",   FILTER_SANITIZE_SPECIAL_CHARS, php_filter_special_chars   },
54 	{ "full_special_chars",   FILTER_SANITIZE_FULL_SPECIAL_CHARS, php_filter_full_special_chars   },
55 	{ "unsafe_raw",      FILTER_UNSAFE_RAW,             php_filter_unsafe_raw      },
56 	{ "email",           FILTER_SANITIZE_EMAIL,         php_filter_email           },
57 	{ "url",             FILTER_SANITIZE_URL,           php_filter_url             },
58 	{ "number_int",      FILTER_SANITIZE_NUMBER_INT,    php_filter_number_int      },
59 	{ "number_float",    FILTER_SANITIZE_NUMBER_FLOAT,  php_filter_number_float    },
60 	{ "add_slashes",     FILTER_SANITIZE_ADD_SLASHES,   php_filter_add_slashes     },
61 
62 	{ "callback",        FILTER_CALLBACK,               php_filter_callback        },
63 };
64 /* }}} */
65 
66 #ifndef PARSE_ENV
67 #define PARSE_ENV 4
68 #endif
69 
70 #ifndef PARSE_SERVER
71 #define PARSE_SERVER 5
72 #endif
73 
74 #ifndef PARSE_SESSION
75 #define PARSE_SESSION 6
76 #endif
77 
78 static unsigned int php_sapi_filter(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len);
79 static unsigned int php_sapi_filter_init(void);
80 
81 /* {{{ filter_module_entry */
82 zend_module_entry filter_module_entry = {
83 	STANDARD_MODULE_HEADER,
84 	"filter",
85 	ext_functions,
86 	PHP_MINIT(filter),
87 	PHP_MSHUTDOWN(filter),
88 	NULL,
89 	PHP_RSHUTDOWN(filter),
90 	PHP_MINFO(filter),
91 	PHP_FILTER_VERSION,
92 	STANDARD_MODULE_PROPERTIES
93 };
94 /* }}} */
95 
96 #ifdef COMPILE_DL_FILTER
97 #ifdef ZTS
98 ZEND_TSRMLS_CACHE_DEFINE()
99 #endif
ZEND_GET_MODULE(filter)100 ZEND_GET_MODULE(filter)
101 #endif
102 
103 static PHP_INI_MH(UpdateDefaultFilter) /* {{{ */
104 {
105 	int i, size = sizeof(filter_list) / sizeof(filter_list_entry);
106 
107 	for (i = 0; i < size; ++i) {
108 		if ((strcasecmp(ZSTR_VAL(new_value), filter_list[i].name) == 0)) {
109 			IF_G(default_filter) = filter_list[i].id;
110 			if (IF_G(default_filter) != FILTER_DEFAULT) {
111 				zend_error(E_DEPRECATED, "The filter.default ini setting is deprecated");
112 			}
113 			return SUCCESS;
114 		}
115 	}
116 	/* Fallback to the default filter */
117 	IF_G(default_filter) = FILTER_DEFAULT;
118 	return SUCCESS;
119 }
120 /* }}} */
121 
122 /* {{{ PHP_INI */
PHP_INI_MH(OnUpdateFlags)123 static PHP_INI_MH(OnUpdateFlags)
124 {
125 	if (!new_value) {
126 		IF_G(default_filter_flags) = FILTER_FLAG_NO_ENCODE_QUOTES;
127 	} else {
128 		IF_G(default_filter_flags) = atoi(ZSTR_VAL(new_value));
129 	}
130 	return SUCCESS;
131 }
132 
133 PHP_INI_BEGIN()
134 	STD_PHP_INI_ENTRY("filter.default",   "unsafe_raw", PHP_INI_SYSTEM|PHP_INI_PERDIR, UpdateDefaultFilter, default_filter, zend_filter_globals, filter_globals)
135 	PHP_INI_ENTRY("filter.default_flags", NULL,     PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateFlags)
PHP_INI_END()136 PHP_INI_END()
137 /* }}} */
138 
139 static void php_filter_init_globals(zend_filter_globals *filter_globals) /* {{{ */
140 {
141 #if defined(COMPILE_DL_FILTER) && defined(ZTS)
142 ZEND_TSRMLS_CACHE_UPDATE();
143 #endif
144 	ZVAL_UNDEF(&filter_globals->post_array);
145 	ZVAL_UNDEF(&filter_globals->get_array);
146 	ZVAL_UNDEF(&filter_globals->cookie_array);
147 	ZVAL_UNDEF(&filter_globals->env_array);
148 	ZVAL_UNDEF(&filter_globals->server_array);
149 #if 0
150 	ZVAL_UNDEF(&filter_globals->session_array);
151 #endif
152 	filter_globals->default_filter = FILTER_DEFAULT;
153 }
154 /* }}} */
155 
156 #define PARSE_REQUEST 99
157 
158 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(filter)159 PHP_MINIT_FUNCTION(filter)
160 {
161 	ZEND_INIT_MODULE_GLOBALS(filter, php_filter_init_globals, NULL);
162 
163 	REGISTER_INI_ENTRIES();
164 
165 	REGISTER_LONG_CONSTANT("INPUT_POST",	PARSE_POST, 	CONST_CS | CONST_PERSISTENT);
166 	REGISTER_LONG_CONSTANT("INPUT_GET",		PARSE_GET,		CONST_CS | CONST_PERSISTENT);
167 	REGISTER_LONG_CONSTANT("INPUT_COOKIE",	PARSE_COOKIE, 	CONST_CS | CONST_PERSISTENT);
168 	REGISTER_LONG_CONSTANT("INPUT_ENV",		PARSE_ENV,		CONST_CS | CONST_PERSISTENT);
169 	REGISTER_LONG_CONSTANT("INPUT_SERVER",	PARSE_SERVER, 	CONST_CS | CONST_PERSISTENT);
170 
171 	REGISTER_LONG_CONSTANT("FILTER_FLAG_NONE", FILTER_FLAG_NONE, CONST_CS | CONST_PERSISTENT);
172 
173 	REGISTER_LONG_CONSTANT("FILTER_REQUIRE_SCALAR", FILTER_REQUIRE_SCALAR, CONST_CS | CONST_PERSISTENT);
174 	REGISTER_LONG_CONSTANT("FILTER_REQUIRE_ARRAY", FILTER_REQUIRE_ARRAY, CONST_CS | CONST_PERSISTENT);
175 	REGISTER_LONG_CONSTANT("FILTER_FORCE_ARRAY", FILTER_FORCE_ARRAY, CONST_CS | CONST_PERSISTENT);
176 	REGISTER_LONG_CONSTANT("FILTER_NULL_ON_FAILURE", FILTER_NULL_ON_FAILURE, CONST_CS | CONST_PERSISTENT);
177 
178 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_INT", FILTER_VALIDATE_INT, CONST_CS | CONST_PERSISTENT);
179 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_BOOLEAN", FILTER_VALIDATE_BOOL, CONST_CS | CONST_PERSISTENT);
180 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_BOOL", FILTER_VALIDATE_BOOL, CONST_CS | CONST_PERSISTENT);
181 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_FLOAT", FILTER_VALIDATE_FLOAT, CONST_CS | CONST_PERSISTENT);
182 
183 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_REGEXP", FILTER_VALIDATE_REGEXP, CONST_CS | CONST_PERSISTENT);
184 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_DOMAIN", FILTER_VALIDATE_DOMAIN, CONST_CS | CONST_PERSISTENT);
185 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_URL", FILTER_VALIDATE_URL, CONST_CS | CONST_PERSISTENT);
186 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_EMAIL", FILTER_VALIDATE_EMAIL, CONST_CS | CONST_PERSISTENT);
187 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_IP", FILTER_VALIDATE_IP, CONST_CS | CONST_PERSISTENT);
188 	REGISTER_LONG_CONSTANT("FILTER_VALIDATE_MAC", FILTER_VALIDATE_MAC, CONST_CS | CONST_PERSISTENT);
189 
190 	REGISTER_LONG_CONSTANT("FILTER_DEFAULT", FILTER_DEFAULT, CONST_CS | CONST_PERSISTENT);
191 	REGISTER_LONG_CONSTANT("FILTER_UNSAFE_RAW", FILTER_UNSAFE_RAW, CONST_CS | CONST_PERSISTENT);
192 
193 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_STRING", FILTER_SANITIZE_STRING, CONST_CS | CONST_PERSISTENT | CONST_DEPRECATED);
194 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_STRIPPED", FILTER_SANITIZE_STRING, CONST_CS | CONST_PERSISTENT | CONST_DEPRECATED);
195 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_ENCODED", FILTER_SANITIZE_ENCODED, CONST_CS | CONST_PERSISTENT);
196 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_SPECIAL_CHARS", FILTER_SANITIZE_SPECIAL_CHARS, CONST_CS | CONST_PERSISTENT);
197 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_FULL_SPECIAL_CHARS", FILTER_SANITIZE_FULL_SPECIAL_CHARS, CONST_CS | CONST_PERSISTENT);
198 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_EMAIL", FILTER_SANITIZE_EMAIL, CONST_CS | CONST_PERSISTENT);
199 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_URL", FILTER_SANITIZE_URL, CONST_CS | CONST_PERSISTENT);
200 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_NUMBER_INT", FILTER_SANITIZE_NUMBER_INT, CONST_CS | CONST_PERSISTENT);
201 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_NUMBER_FLOAT", FILTER_SANITIZE_NUMBER_FLOAT, CONST_CS | CONST_PERSISTENT);
202 	REGISTER_LONG_CONSTANT("FILTER_SANITIZE_ADD_SLASHES", FILTER_SANITIZE_ADD_SLASHES, CONST_CS | CONST_PERSISTENT);
203 
204 	REGISTER_LONG_CONSTANT("FILTER_CALLBACK", FILTER_CALLBACK, CONST_CS | CONST_PERSISTENT);
205 
206 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ALLOW_OCTAL", FILTER_FLAG_ALLOW_OCTAL, CONST_CS | CONST_PERSISTENT);
207 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ALLOW_HEX", FILTER_FLAG_ALLOW_HEX, CONST_CS | CONST_PERSISTENT);
208 
209 	REGISTER_LONG_CONSTANT("FILTER_FLAG_STRIP_LOW", FILTER_FLAG_STRIP_LOW, CONST_CS | CONST_PERSISTENT);
210 	REGISTER_LONG_CONSTANT("FILTER_FLAG_STRIP_HIGH", FILTER_FLAG_STRIP_HIGH, CONST_CS | CONST_PERSISTENT);
211 	REGISTER_LONG_CONSTANT("FILTER_FLAG_STRIP_BACKTICK", FILTER_FLAG_STRIP_BACKTICK, CONST_CS | CONST_PERSISTENT);
212 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ENCODE_LOW", FILTER_FLAG_ENCODE_LOW, CONST_CS | CONST_PERSISTENT);
213 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ENCODE_HIGH", FILTER_FLAG_ENCODE_HIGH, CONST_CS | CONST_PERSISTENT);
214 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ENCODE_AMP", FILTER_FLAG_ENCODE_AMP, CONST_CS | CONST_PERSISTENT);
215 	REGISTER_LONG_CONSTANT("FILTER_FLAG_NO_ENCODE_QUOTES", FILTER_FLAG_NO_ENCODE_QUOTES, CONST_CS | CONST_PERSISTENT);
216 	REGISTER_LONG_CONSTANT("FILTER_FLAG_EMPTY_STRING_NULL", FILTER_FLAG_EMPTY_STRING_NULL, CONST_CS | CONST_PERSISTENT);
217 
218 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ALLOW_FRACTION", FILTER_FLAG_ALLOW_FRACTION, CONST_CS | CONST_PERSISTENT);
219 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ALLOW_THOUSAND", FILTER_FLAG_ALLOW_THOUSAND, CONST_CS | CONST_PERSISTENT);
220 	REGISTER_LONG_CONSTANT("FILTER_FLAG_ALLOW_SCIENTIFIC", FILTER_FLAG_ALLOW_SCIENTIFIC, CONST_CS | CONST_PERSISTENT);
221 
222 	REGISTER_LONG_CONSTANT("FILTER_FLAG_PATH_REQUIRED", FILTER_FLAG_PATH_REQUIRED, CONST_CS | CONST_PERSISTENT);
223 	REGISTER_LONG_CONSTANT("FILTER_FLAG_QUERY_REQUIRED", FILTER_FLAG_QUERY_REQUIRED, CONST_CS | CONST_PERSISTENT);
224 
225 	REGISTER_LONG_CONSTANT("FILTER_FLAG_IPV4", FILTER_FLAG_IPV4, CONST_CS | CONST_PERSISTENT);
226 	REGISTER_LONG_CONSTANT("FILTER_FLAG_IPV6", FILTER_FLAG_IPV6, CONST_CS | CONST_PERSISTENT);
227 	REGISTER_LONG_CONSTANT("FILTER_FLAG_NO_RES_RANGE", FILTER_FLAG_NO_RES_RANGE, CONST_CS | CONST_PERSISTENT);
228 	REGISTER_LONG_CONSTANT("FILTER_FLAG_NO_PRIV_RANGE", FILTER_FLAG_NO_PRIV_RANGE, CONST_CS | CONST_PERSISTENT);
229 
230 	REGISTER_LONG_CONSTANT("FILTER_FLAG_HOSTNAME", FILTER_FLAG_HOSTNAME, CONST_CS | CONST_PERSISTENT);
231 
232 	REGISTER_LONG_CONSTANT("FILTER_FLAG_EMAIL_UNICODE", FILTER_FLAG_EMAIL_UNICODE, CONST_CS | CONST_PERSISTENT);
233 
234 	sapi_register_input_filter(php_sapi_filter, php_sapi_filter_init);
235 
236 	return SUCCESS;
237 }
238 /* }}} */
239 
240 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(filter)241 PHP_MSHUTDOWN_FUNCTION(filter)
242 {
243 	UNREGISTER_INI_ENTRIES();
244 
245 	return SUCCESS;
246 }
247 /* }}} */
248 
249 /* {{{ PHP_RSHUTDOWN_FUNCTION */
250 #define VAR_ARRAY_COPY_DTOR(a)   \
251 	if (!Z_ISUNDEF(IF_G(a))) {   \
252 		zval_ptr_dtor(&IF_G(a)); \
253 		ZVAL_UNDEF(&IF_G(a));    \
254 	}
255 
PHP_RSHUTDOWN_FUNCTION(filter)256 PHP_RSHUTDOWN_FUNCTION(filter)
257 {
258 	VAR_ARRAY_COPY_DTOR(get_array)
259 	VAR_ARRAY_COPY_DTOR(post_array)
260 	VAR_ARRAY_COPY_DTOR(cookie_array)
261 	VAR_ARRAY_COPY_DTOR(server_array)
262 	VAR_ARRAY_COPY_DTOR(env_array)
263 #if 0
264 	VAR_ARRAY_COPY_DTOR(session_array)
265 #endif
266 	return SUCCESS;
267 }
268 /* }}} */
269 
270 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(filter)271 PHP_MINFO_FUNCTION(filter)
272 {
273 	php_info_print_table_start();
274 	php_info_print_table_row( 2, "Input Validation and Filtering", "enabled" );
275 	php_info_print_table_end();
276 
277 	DISPLAY_INI_ENTRIES();
278 }
279 /* }}} */
280 
php_find_filter(zend_long id)281 static filter_list_entry php_find_filter(zend_long id) /* {{{ */
282 {
283 	int i, size = sizeof(filter_list) / sizeof(filter_list_entry);
284 
285 	for (i = 0; i < size; ++i) {
286 		if (filter_list[i].id == id) {
287 			return filter_list[i];
288 		}
289 	}
290 	/* Fallback to "string" filter */
291 	for (i = 0; i < size; ++i) {
292 		if (filter_list[i].id == FILTER_DEFAULT) {
293 			return filter_list[i];
294 		}
295 	}
296 	/* To shut up GCC */
297 	return filter_list[0];
298 }
299 /* }}} */
300 
php_sapi_filter_init(void)301 static unsigned int php_sapi_filter_init(void)
302 {
303 	ZVAL_UNDEF(&IF_G(get_array));
304 	ZVAL_UNDEF(&IF_G(post_array));
305 	ZVAL_UNDEF(&IF_G(cookie_array));
306 	ZVAL_UNDEF(&IF_G(server_array));
307 	ZVAL_UNDEF(&IF_G(env_array));
308 #if 0
309 	ZVAL_UNDEF(&IF_G(session_array));
310 #endif
311 	return SUCCESS;
312 }
313 
php_zval_filter(zval * value,zend_long filter,zend_long flags,zval * options,char * charset,bool copy)314 static void php_zval_filter(zval *value, zend_long filter, zend_long flags, zval *options, char* charset, bool copy) /* {{{ */
315 {
316 	filter_list_entry  filter_func;
317 
318 	filter_func = php_find_filter(filter);
319 
320 	if (!filter_func.id) {
321 		/* Find default filter */
322 		filter_func = php_find_filter(FILTER_DEFAULT);
323 	}
324 
325 	/* #49274, fatal error with object without a toString method
326 	  Fails nicely instead of getting a recovarable fatal error. */
327 	if (Z_TYPE_P(value) == IS_OBJECT) {
328 		zend_class_entry *ce;
329 
330 		ce = Z_OBJCE_P(value);
331 		if (!ce->__tostring) {
332 			zval_ptr_dtor(value);
333 			/* #67167: doesn't return null on failure for objects */
334 			if (flags & FILTER_NULL_ON_FAILURE) {
335 				ZVAL_NULL(value);
336 			} else {
337 				ZVAL_FALSE(value);
338 			}
339 			goto handle_default;
340 		}
341 	}
342 
343 	/* Here be strings */
344 	convert_to_string(value);
345 
346 	filter_func.function(value, flags, options, charset);
347 
348 handle_default:
349 	if (options && Z_TYPE_P(options) == IS_ARRAY &&
350 		((flags & FILTER_NULL_ON_FAILURE && Z_TYPE_P(value) == IS_NULL) ||
351 		(!(flags & FILTER_NULL_ON_FAILURE) && Z_TYPE_P(value) == IS_FALSE))) {
352 		zval *tmp;
353 		if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "default", sizeof("default") - 1)) != NULL) {
354 			ZVAL_COPY(value, tmp);
355 		}
356 	}
357 }
358 /* }}} */
359 
php_sapi_filter(int arg,const char * var,char ** val,size_t val_len,size_t * new_val_len)360 static unsigned int php_sapi_filter(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len) /* {{{ */
361 {
362 	zval  new_var, raw_var;
363 	zval *array_ptr = NULL, *orig_array_ptr = NULL;
364 	int retval = 0;
365 
366 	assert(*val != NULL);
367 
368 #define PARSE_CASE(s,a,t)                     		\
369 		case s:                               		\
370 			if (Z_ISUNDEF(IF_G(a))) {         		\
371 				array_init(&IF_G(a)); 				\
372 			}										\
373 			array_ptr = &IF_G(a);          			\
374 			orig_array_ptr = &PG(http_globals)[t]; 	\
375 			break;
376 
377 	switch (arg) {
378 		PARSE_CASE(PARSE_POST,    post_array,    TRACK_VARS_POST)
379 		PARSE_CASE(PARSE_GET,     get_array,     TRACK_VARS_GET)
380 		PARSE_CASE(PARSE_COOKIE,  cookie_array,  TRACK_VARS_COOKIE)
381 		PARSE_CASE(PARSE_SERVER,  server_array,  TRACK_VARS_SERVER)
382 		PARSE_CASE(PARSE_ENV,     env_array,     TRACK_VARS_ENV)
383 
384 		case PARSE_STRING: /* PARSE_STRING is used by parse_str() function */
385 			retval = 1;
386 			break;
387 	}
388 
389 	/*
390 	 * According to rfc2965, more specific paths are listed above the less specific ones.
391 	 * If we encounter a duplicate cookie name, we should skip it, since it is not possible
392 	 * to have the same (plain text) cookie name for the same path and we should not overwrite
393 	 * more specific cookies with the less specific ones.
394 	*/
395 	if (arg == PARSE_COOKIE && orig_array_ptr &&
396 			zend_symtable_str_exists(Z_ARRVAL_P(orig_array_ptr), var, strlen(var))) {
397 		return 0;
398 	}
399 
400 	if (array_ptr) {
401 		/* Store the RAW variable internally */
402 		ZVAL_STRINGL(&raw_var, *val, val_len);
403 		php_register_variable_ex(var, &raw_var, array_ptr);
404 	}
405 
406 	if (val_len) {
407 		/* Register mangled variable */
408 		if (IF_G(default_filter) != FILTER_UNSAFE_RAW) {
409 			ZVAL_STRINGL(&new_var, *val, val_len);
410 			php_zval_filter(&new_var, IF_G(default_filter), IF_G(default_filter_flags), NULL, NULL, 0);
411 		} else {
412 			ZVAL_STRINGL(&new_var, *val, val_len);
413 		}
414 	} else { /* empty string */
415 		ZVAL_EMPTY_STRING(&new_var);
416 	}
417 
418 	if (orig_array_ptr) {
419 		php_register_variable_ex(var, &new_var, orig_array_ptr);
420 	}
421 
422 	if (retval) {
423 		if (new_val_len) {
424 			*new_val_len = Z_STRLEN(new_var);
425 		}
426 		efree(*val);
427 		if (Z_STRLEN(new_var)) {
428 			*val = estrndup(Z_STRVAL(new_var), Z_STRLEN(new_var));
429 		} else {
430 			*val = estrdup("");
431 		}
432 		zval_ptr_dtor(&new_var);
433 	}
434 
435 	return retval;
436 }
437 /* }}} */
438 
php_zval_filter_recursive(zval * value,zend_long filter,zend_long flags,zval * options,char * charset,bool copy)439 static void php_zval_filter_recursive(zval *value, zend_long filter, zend_long flags, zval *options, char *charset, bool copy) /* {{{ */
440 {
441 	if (Z_TYPE_P(value) == IS_ARRAY) {
442 		zval *element;
443 
444 		if (Z_IS_RECURSIVE_P(value)) {
445 			return;
446 		}
447 		Z_PROTECT_RECURSION_P(value);
448 
449 		ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), element) {
450 			ZVAL_DEREF(element);
451 			if (Z_TYPE_P(element) == IS_ARRAY) {
452 				SEPARATE_ARRAY(element);
453 				php_zval_filter_recursive(element, filter, flags, options, charset, copy);
454 			} else {
455 				php_zval_filter(element, filter, flags, options, charset, copy);
456 			}
457 		} ZEND_HASH_FOREACH_END();
458 		Z_UNPROTECT_RECURSION_P(value);
459 	} else {
460 		php_zval_filter(value, filter, flags, options, charset, copy);
461 	}
462 }
463 /* }}} */
464 
php_filter_get_storage(zend_long arg)465 static zval *php_filter_get_storage(zend_long arg)/* {{{ */
466 
467 {
468 	zval *array_ptr = NULL;
469 
470 	switch (arg) {
471 		case PARSE_GET:
472 			array_ptr = &IF_G(get_array);
473 			break;
474 		case PARSE_POST:
475 			array_ptr = &IF_G(post_array);
476 			break;
477 		case PARSE_COOKIE:
478 			array_ptr = &IF_G(cookie_array);
479 			break;
480 		case PARSE_SERVER:
481 			if (PG(auto_globals_jit)) {
482 				zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER));
483 			}
484 			array_ptr = &IF_G(server_array);
485 			break;
486 		case PARSE_ENV:
487 			if (PG(auto_globals_jit)) {
488 				zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_ENV));
489 			}
490 			array_ptr = !Z_ISUNDEF(IF_G(env_array)) ? &IF_G(env_array) : &PG(http_globals)[TRACK_VARS_ENV];
491 			break;
492 		default:
493 			zend_argument_value_error(1, "must be an INPUT_* constant");
494 			return NULL;
495 	}
496 
497 	if (array_ptr && Z_TYPE_P(array_ptr) != IS_ARRAY) {
498 		/* Storage not initialized */
499 		return NULL;
500 	}
501 
502 	return array_ptr;
503 }
504 /* }}} */
505 
506 /* {{{ Returns true if the variable with the name 'name' exists in source. */
PHP_FUNCTION(filter_has_var)507 PHP_FUNCTION(filter_has_var)
508 {
509 	zend_long         arg;
510 	zend_string *var;
511 	zval        *array_ptr = NULL;
512 
513 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "lS", &arg, &var) == FAILURE) {
514 		RETURN_THROWS();
515 	}
516 
517 	array_ptr = php_filter_get_storage(arg);
518 	if (EG(exception)) {
519 		RETURN_THROWS();
520 	}
521 
522 	if (array_ptr && zend_hash_exists(Z_ARRVAL_P(array_ptr), var)) {
523 		RETURN_TRUE;
524 	}
525 
526 	RETURN_FALSE;
527 }
528 /* }}} */
529 
php_filter_call(zval * filtered,zend_long filter,HashTable * filter_args_ht,zend_long filter_args_long,const int copy,zend_long filter_flags)530 static void php_filter_call(
531 	zval *filtered, zend_long filter, HashTable *filter_args_ht, zend_long filter_args_long,
532 	const int copy, zend_long filter_flags
533 ) /* {{{ */ {
534 	zval *options = NULL;
535 	zval *option;
536 	char *charset = NULL;
537 
538 	if (!filter_args_ht) {
539 		if (filter != -1) { /* handler for array apply */
540 			/* filter_args is the filter_flags */
541 			filter_flags = filter_args_long;
542 
543 			if (!(filter_flags & FILTER_REQUIRE_ARRAY ||  filter_flags & FILTER_FORCE_ARRAY)) {
544 				filter_flags |= FILTER_REQUIRE_SCALAR;
545 			}
546 		} else {
547 			filter = filter_args_long;
548 		}
549 	} else {
550 		if ((option = zend_hash_str_find(filter_args_ht, "filter", sizeof("filter") - 1)) != NULL) {
551 			filter = zval_get_long(option);
552 		}
553 
554 		if ((option = zend_hash_str_find_deref(filter_args_ht, "options", sizeof("options") - 1)) != NULL) {
555 			if (filter != FILTER_CALLBACK) {
556 				if (Z_TYPE_P(option) == IS_ARRAY) {
557 					options = option;
558 				}
559 			} else {
560 				options = option;
561 				filter_flags = 0;
562 			}
563 		}
564 
565 		if ((option = zend_hash_str_find(filter_args_ht, "flags", sizeof("flags") - 1)) != NULL) {
566 			filter_flags = zval_get_long(option);
567 
568 			if (!(filter_flags & FILTER_REQUIRE_ARRAY ||  filter_flags & FILTER_FORCE_ARRAY)) {
569 				filter_flags |= FILTER_REQUIRE_SCALAR;
570 			}
571 		}
572 	}
573 
574 	if (Z_TYPE_P(filtered) == IS_ARRAY) {
575 		if (filter_flags & FILTER_REQUIRE_SCALAR) {
576 			zval_ptr_dtor(filtered);
577 			if (filter_flags & FILTER_NULL_ON_FAILURE) {
578 				ZVAL_NULL(filtered);
579 			} else {
580 				ZVAL_FALSE(filtered);
581 			}
582 			return;
583 		}
584 		php_zval_filter_recursive(filtered, filter, filter_flags, options, charset, copy);
585 		return;
586 	}
587 	if (filter_flags & FILTER_REQUIRE_ARRAY) {
588 		zval_ptr_dtor(filtered);
589 		if (filter_flags & FILTER_NULL_ON_FAILURE) {
590 			ZVAL_NULL(filtered);
591 		} else {
592 			ZVAL_FALSE(filtered);
593 		}
594 		return;
595 	}
596 
597 	php_zval_filter(filtered, filter, filter_flags, options, charset, copy);
598 	if (filter_flags & FILTER_FORCE_ARRAY) {
599 		zval tmp;
600 		ZVAL_COPY_VALUE(&tmp, filtered);
601 		array_init(filtered);
602 		add_next_index_zval(filtered, &tmp);
603 	}
604 }
605 /* }}} */
606 
php_filter_array_handler(zval * input,HashTable * op_ht,zend_long op_long,zval * return_value,bool add_empty)607 static void php_filter_array_handler(zval *input, HashTable *op_ht, zend_long op_long,
608 	zval *return_value, bool add_empty
609 ) /* {{{ */ {
610 	zend_string *arg_key;
611 	zval *tmp, *arg_elm;
612 
613 	if (!op_ht) {
614 		ZVAL_DUP(return_value, input);
615 		php_filter_call(return_value, -1, NULL, op_long, 0, FILTER_REQUIRE_ARRAY);
616 	} else {
617 		array_init(return_value);
618 
619 		ZEND_HASH_FOREACH_STR_KEY_VAL(op_ht, arg_key, arg_elm) {
620 			if (arg_key == NULL) {
621 				zend_argument_type_error(2, "must contain only string keys");
622 				RETURN_THROWS();
623 	 		}
624 			if (ZSTR_LEN(arg_key) == 0) {
625 				zend_argument_value_error(2, "cannot contain empty keys");
626 				RETURN_THROWS();
627 			}
628 			if ((tmp = zend_hash_find(Z_ARRVAL_P(input), arg_key)) == NULL) {
629 				if (add_empty) {
630 					add_assoc_null_ex(return_value, ZSTR_VAL(arg_key), ZSTR_LEN(arg_key));
631 				}
632 			} else {
633 				zval nval;
634 				ZVAL_DEREF(tmp);
635 				ZVAL_DUP(&nval, tmp);
636 				php_filter_call(&nval, -1,
637 					Z_TYPE_P(arg_elm) == IS_ARRAY ? Z_ARRVAL_P(arg_elm) : NULL,
638 					Z_TYPE_P(arg_elm) == IS_ARRAY ? 0 : zval_get_long(arg_elm),
639 					0, FILTER_REQUIRE_SCALAR
640 				);
641 				zend_hash_update(Z_ARRVAL_P(return_value), arg_key, &nval);
642 			}
643 		} ZEND_HASH_FOREACH_END();
644 	}
645 }
646 /* }}} */
647 
648 /* {{{ Returns the filtered variable 'name'* from source `type`. */
PHP_FUNCTION(filter_input)649 PHP_FUNCTION(filter_input)
650 {
651 	zend_long fetch_from, filter = FILTER_DEFAULT;
652 	zval *input = NULL, *tmp;
653 	zend_string *var;
654 	HashTable *filter_args_ht = NULL;
655 	zend_long filter_args_long = 0;
656 
657 	ZEND_PARSE_PARAMETERS_START(2, 4)
658 		Z_PARAM_LONG(fetch_from)
659 		Z_PARAM_STR(var)
660 		Z_PARAM_OPTIONAL
661 		Z_PARAM_LONG(filter)
662 		Z_PARAM_ARRAY_HT_OR_LONG(filter_args_ht, filter_args_long)
663 	ZEND_PARSE_PARAMETERS_END();
664 
665 	if (!PHP_FILTER_ID_EXISTS(filter)) {
666 		php_error_docref(NULL, E_WARNING, "Unknown filter with ID " ZEND_LONG_FMT, filter);
667 		RETURN_FALSE;
668 	}
669 
670 	input = php_filter_get_storage(fetch_from);
671 	if (EG(exception)) {
672 		RETURN_THROWS();
673 	}
674 
675 	if (!input || (tmp = zend_hash_find(Z_ARRVAL_P(input), var)) == NULL) {
676 		zend_long filter_flags = 0;
677 		zval *option, *opt, *def;
678 		if (!filter_args_ht) {
679 			filter_flags = filter_args_long;
680 		} else {
681 			if ((option = zend_hash_str_find(filter_args_ht, "flags", sizeof("flags") - 1)) != NULL) {
682 				filter_flags = zval_get_long(option);
683 			}
684 
685 			if ((opt = zend_hash_str_find_deref(filter_args_ht, "options", sizeof("options") - 1)) != NULL &&
686 				Z_TYPE_P(opt) == IS_ARRAY &&
687 				(def = zend_hash_str_find_deref(Z_ARRVAL_P(opt), "default", sizeof("default") - 1)) != NULL
688 			) {
689 				ZVAL_COPY(return_value, def);
690 				return;
691 			}
692 		}
693 
694 		/* The FILTER_NULL_ON_FAILURE flag inverts the usual return values of
695 		 * the function: normally when validation fails false is returned, and
696 		 * when the input value doesn't exist NULL is returned. With the flag
697 		 * set, NULL and false should be returned, respectively. Ergo, although
698 		 * the code below looks incorrect, it's actually right. */
699 		if (filter_flags & FILTER_NULL_ON_FAILURE) {
700 			RETURN_FALSE;
701 		} else {
702 			RETURN_NULL();
703 		}
704 	}
705 
706 	ZVAL_DUP(return_value, tmp);
707 
708 	php_filter_call(return_value, filter, filter_args_ht, filter_args_long, 1, FILTER_REQUIRE_SCALAR);
709 }
710 /* }}} */
711 
712 /* {{{ Returns the filtered version of the variable. */
PHP_FUNCTION(filter_var)713 PHP_FUNCTION(filter_var)
714 {
715 	zend_long filter = FILTER_DEFAULT;
716 	zval *data;
717 	HashTable *filter_args_ht = NULL;
718 	zend_long filter_args_long = 0;
719 
720 	ZEND_PARSE_PARAMETERS_START(1, 3)
721 		Z_PARAM_ZVAL(data)
722 		Z_PARAM_OPTIONAL
723 		Z_PARAM_LONG(filter)
724 		Z_PARAM_ARRAY_HT_OR_LONG(filter_args_ht, filter_args_long)
725 	ZEND_PARSE_PARAMETERS_END();
726 
727 	if (!PHP_FILTER_ID_EXISTS(filter)) {
728 		php_error_docref(NULL, E_WARNING, "Unknown filter with ID " ZEND_LONG_FMT, filter);
729 		RETURN_FALSE;
730 	}
731 
732 	ZVAL_DUP(return_value, data);
733 
734 	php_filter_call(return_value, filter, filter_args_ht, filter_args_long, 1, FILTER_REQUIRE_SCALAR);
735 }
736 /* }}} */
737 
738 /* {{{ Returns an array with all arguments defined in 'definition'. */
PHP_FUNCTION(filter_input_array)739 PHP_FUNCTION(filter_input_array)
740 {
741 	zend_long    fetch_from;
742 	zval   *array_input = NULL;
743 	bool add_empty = 1;
744 	HashTable *op_ht = NULL;
745 	zend_long op_long = FILTER_DEFAULT;
746 
747 	ZEND_PARSE_PARAMETERS_START(1, 3)
748 		Z_PARAM_LONG(fetch_from)
749 		Z_PARAM_OPTIONAL
750 		Z_PARAM_ARRAY_HT_OR_LONG(op_ht, op_long)
751 		Z_PARAM_BOOL(add_empty)
752 	ZEND_PARSE_PARAMETERS_END();
753 
754 	if (!op_ht && !PHP_FILTER_ID_EXISTS(op_long)) {
755 		php_error_docref(NULL, E_WARNING, "Unknown filter with ID " ZEND_LONG_FMT, op_long);
756 		RETURN_FALSE;
757 	}
758 
759 	array_input = php_filter_get_storage(fetch_from);
760 	if (EG(exception)) {
761 		RETURN_THROWS();
762 	}
763 
764 	if (!array_input) {
765 		zend_long filter_flags = 0;
766 		zval *option;
767 		if (op_long) {
768 			filter_flags = op_long;
769 		} else if (op_ht && (option = zend_hash_str_find(op_ht, "flags", sizeof("flags") - 1)) != NULL) {
770 			filter_flags = zval_get_long(option);
771 		}
772 
773 		/* The FILTER_NULL_ON_FAILURE flag inverts the usual return values of
774 		 * the function: normally when validation fails false is returned, and
775 		 * when the input value doesn't exist NULL is returned. With the flag
776 		 * set, NULL and false should be returned, respectively. Ergo, although
777 		 * the code below looks incorrect, it's actually right. */
778 		if (filter_flags & FILTER_NULL_ON_FAILURE) {
779 			RETURN_FALSE;
780 		} else {
781 			RETURN_NULL();
782 		}
783 	}
784 
785 	php_filter_array_handler(array_input, op_ht, op_long, return_value, add_empty);
786 }
787 /* }}} */
788 
789 /* {{{ Returns an array with all arguments defined in 'definition'. */
PHP_FUNCTION(filter_var_array)790 PHP_FUNCTION(filter_var_array)
791 {
792 	zval *array_input = NULL;
793 	bool add_empty = 1;
794 	HashTable *op_ht = NULL;
795 	zend_long op_long = FILTER_DEFAULT;
796 
797 	ZEND_PARSE_PARAMETERS_START(1, 3)
798 		Z_PARAM_ARRAY(array_input)
799 		Z_PARAM_OPTIONAL
800 		Z_PARAM_ARRAY_HT_OR_LONG(op_ht, op_long)
801 		Z_PARAM_BOOL(add_empty)
802 	ZEND_PARSE_PARAMETERS_END();
803 
804 	if (!op_ht && !PHP_FILTER_ID_EXISTS(op_long)) {
805 		php_error_docref(NULL, E_WARNING, "Unknown filter with ID " ZEND_LONG_FMT, op_long);
806 		RETURN_FALSE;
807 	}
808 
809 	php_filter_array_handler(array_input, op_ht, op_long, return_value, add_empty);
810 }
811 /* }}} */
812 
813 /* {{{ Returns a list of all supported filters */
PHP_FUNCTION(filter_list)814 PHP_FUNCTION(filter_list)
815 {
816 	int i, size = sizeof(filter_list) / sizeof(filter_list_entry);
817 
818 	if (zend_parse_parameters_none() == FAILURE) {
819 		RETURN_THROWS();
820 	}
821 
822 	array_init(return_value);
823 	for (i = 0; i < size; ++i) {
824 		add_next_index_string(return_value, (char *)filter_list[i].name);
825 	}
826 }
827 /* }}} */
828 
829 /* {{{ Returns the filter ID belonging to a named filter */
PHP_FUNCTION(filter_id)830 PHP_FUNCTION(filter_id)
831 {
832 	int i;
833 	size_t filter_len;
834 	int size = sizeof(filter_list) / sizeof(filter_list_entry);
835 	char *filter;
836 
837 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &filter, &filter_len) == FAILURE) {
838 		RETURN_THROWS();
839 	}
840 
841 	for (i = 0; i < size; ++i) {
842 		if (strcmp(filter_list[i].name, filter) == 0) {
843 			RETURN_LONG(filter_list[i].id);
844 		}
845 	}
846 
847 	RETURN_FALSE;
848 }
849 /* }}} */
850