xref: /PHP-5.6/ext/filter/logical_filters.c (revision 9834978a)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Derick Rethans <derick@php.net>                             |
16   |          Pierre-A. Joye <pierre@php.net>                             |
17   +----------------------------------------------------------------------+
18 */
19 
20 /* $Id$ */
21 
22 #include "php_filter.h"
23 #include "filter_private.h"
24 #include "ext/standard/url.h"
25 #include "ext/pcre/php_pcre.h"
26 
27 #include "zend_multiply.h"
28 
29 #if HAVE_ARPA_INET_H
30 # include <arpa/inet.h>
31 #endif
32 
33 #ifndef INADDR_NONE
34 # define INADDR_NONE ((unsigned long int) -1)
35 #endif
36 
37 
38 /* {{{ FETCH_LONG_OPTION(var_name, option_name) */
39 #define FETCH_LONG_OPTION(var_name, option_name)                                                                         \
40 	var_name = 0;                                                                                                        \
41 	var_name##_set = 0;                                                                                                  \
42 	if (option_array) {                                                                                                  \
43 		if (zend_hash_find(HASH_OF(option_array), option_name, sizeof(option_name), (void **) &option_val) == SUCCESS) { \
44 			PHP_FILTER_GET_LONG_OPT(option_val, var_name);								\
45 			var_name##_set = 1;                                                                                          \
46 		}                                                                                                                \
47 	}
48 /* }}} */
49 
50 /* {{{ FETCH_STRING_OPTION(var_name, option_name) */
51 #define FETCH_STRING_OPTION(var_name, option_name)                                                                       \
52 	var_name = NULL;                                                                                                     \
53 	var_name##_set = 0;                                                                                                  \
54 	var_name##_len = 0;                                                                                                  \
55 	if (option_array) {                                                                                                  \
56 		if (zend_hash_find(HASH_OF(option_array), option_name, sizeof(option_name), (void **) &option_val) == SUCCESS) { \
57 			if (Z_TYPE_PP(option_val) == IS_STRING) {                                                                    \
58 				var_name = Z_STRVAL_PP(option_val);                                                                      \
59 				var_name##_len = Z_STRLEN_PP(option_val);                                                                \
60 				var_name##_set = 1;                                                                                      \
61 			}                                                                                                            \
62 		}                                                                                                                \
63 	}
64 /* }}} */
65 
66 #define FORMAT_IPV4    4
67 #define FORMAT_IPV6    6
68 
php_filter_parse_int(const char * str,unsigned int str_len,long * ret TSRMLS_DC)69 static int php_filter_parse_int(const char *str, unsigned int str_len, long *ret TSRMLS_DC) { /* {{{ */
70 	long ctx_value;
71 	int sign = 0, digit = 0;
72 	const char *end = str + str_len;
73 
74 	switch (*str) {
75 		case '-':
76 			sign = 1;
77 		case '+':
78 			str++;
79 		default:
80 			break;
81 	}
82 
83 	if (*str == '0' && str + 1 == end) {
84 		/* Special cases: +0 and -0 */
85 		return 1;
86 	}
87 
88 	/* must start with 1..9*/
89 	if (str < end && *str >= '1' && *str <= '9') {
90 		ctx_value = ((sign)?-1:1) * ((*(str++)) - '0');
91 	} else {
92 		return -1;
93 	}
94 
95 	if ((end - str > MAX_LENGTH_OF_LONG - 1) /* number too long */
96 	 || (SIZEOF_LONG == 4 && (end - str == MAX_LENGTH_OF_LONG - 1) && *str > '2')) {
97 		/* overflow */
98 		return -1;
99 	}
100 
101 	while (str < end) {
102 		if (*str >= '0' && *str <= '9') {
103 			digit = (*(str++) - '0');
104 			if ( (!sign) && ctx_value <= (LONG_MAX-digit)/10 ) {
105 				ctx_value = (ctx_value * 10) + digit;
106 			} else if ( sign && ctx_value >= (LONG_MIN+digit)/10) {
107 				ctx_value = (ctx_value * 10) - digit;
108 			} else {
109 				return -1;
110 			}
111 		} else {
112 			return -1;
113 		}
114 	}
115 
116 	*ret = ctx_value;
117 	return 1;
118 }
119 /* }}} */
120 
php_filter_parse_octal(const char * str,unsigned int str_len,long * ret TSRMLS_DC)121 static int php_filter_parse_octal(const char *str, unsigned int str_len, long *ret TSRMLS_DC) { /* {{{ */
122 	unsigned long ctx_value = 0;
123 	const char *end = str + str_len;
124 
125 	while (str < end) {
126 		if (*str >= '0' && *str <= '7') {
127 			unsigned long n = ((*(str++)) - '0');
128 
129 			if ((ctx_value > ((unsigned long)(~(long)0)) / 8) ||
130 				((ctx_value = ctx_value * 8) > ((unsigned long)(~(long)0)) - n)) {
131 				return -1;
132 			}
133 			ctx_value += n;
134 		} else {
135 			return -1;
136 		}
137 	}
138 
139 	*ret = (long)ctx_value;
140 	return 1;
141 }
142 /* }}} */
143 
php_filter_parse_hex(const char * str,unsigned int str_len,long * ret TSRMLS_DC)144 static int php_filter_parse_hex(const char *str, unsigned int str_len, long *ret TSRMLS_DC) { /* {{{ */
145 	unsigned long ctx_value = 0;
146 	const char *end = str + str_len;
147 	unsigned long n;
148 
149 	while (str < end) {
150 		if (*str >= '0' && *str <= '9') {
151 			n = ((*(str++)) - '0');
152 		} else if (*str >= 'a' && *str <= 'f') {
153 			n = ((*(str++)) - ('a' - 10));
154 		} else if (*str >= 'A' && *str <= 'F') {
155 			n = ((*(str++)) - ('A' - 10));
156 		} else {
157 			return -1;
158 		}
159 		if ((ctx_value > ((unsigned long)(~(long)0)) / 16) ||
160 			((ctx_value = ctx_value * 16) > ((unsigned long)(~(long)0)) - n)) {
161 			return -1;
162 		}
163 		ctx_value += n;
164 	}
165 
166 	*ret = (long)ctx_value;
167 	return 1;
168 }
169 /* }}} */
170 
php_filter_int(PHP_INPUT_FILTER_PARAM_DECL)171 void php_filter_int(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
172 {
173 	zval **option_val;
174 	long   min_range, max_range, option_flags;
175 	int    min_range_set, max_range_set;
176 	int    allow_octal = 0, allow_hex = 0;
177 	int	   len, error = 0;
178 	long   ctx_value;
179 	char *p;
180 
181 	/* Parse options */
182 	FETCH_LONG_OPTION(min_range,    "min_range");
183 	FETCH_LONG_OPTION(max_range,    "max_range");
184 	option_flags = flags;
185 
186 	len = Z_STRLEN_P(value);
187 
188 	if (len == 0) {
189 		RETURN_VALIDATION_FAILED
190 	}
191 
192 	if (option_flags & FILTER_FLAG_ALLOW_OCTAL) {
193 		allow_octal = 1;
194 	}
195 
196 	if (option_flags & FILTER_FLAG_ALLOW_HEX) {
197 		allow_hex = 1;
198 	}
199 
200 	/* Start the validating loop */
201 	p = Z_STRVAL_P(value);
202 	ctx_value = 0;
203 
204 	PHP_FILTER_TRIM_DEFAULT(p, len);
205 
206 	if (*p == '0') {
207 		p++; len--;
208 		if (allow_hex && (*p == 'x' || *p == 'X')) {
209 			p++; len--;
210 			if (php_filter_parse_hex(p, len, &ctx_value TSRMLS_CC) < 0) {
211 				error = 1;
212 			}
213 		} else if (allow_octal) {
214 			if (php_filter_parse_octal(p, len, &ctx_value TSRMLS_CC) < 0) {
215 				error = 1;
216 			}
217 		} else if (len != 0) {
218 			error = 1;
219 		}
220 	} else {
221 		if (php_filter_parse_int(p, len, &ctx_value TSRMLS_CC) < 0) {
222 			error = 1;
223 		}
224 	}
225 
226 	if (error > 0 || (min_range_set && (ctx_value < min_range)) || (max_range_set && (ctx_value > max_range))) {
227 		RETURN_VALIDATION_FAILED
228 	} else {
229 		zval_dtor(value);
230 		Z_TYPE_P(value) = IS_LONG;
231 		Z_LVAL_P(value) = ctx_value;
232 		return;
233 	}
234 }
235 /* }}} */
236 
php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL)237 void php_filter_boolean(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
238 {
239 	char *str = Z_STRVAL_P(value);
240 	int len = Z_STRLEN_P(value);
241 	int ret;
242 
243 	PHP_FILTER_TRIM_DEFAULT_EX(str, len, 0);
244 
245 	/* returns true for "1", "true", "on" and "yes"
246 	 * returns false for "0", "false", "off", "no", and ""
247 	 * null otherwise. */
248 	switch (len) {
249 		case 0:
250 			ret = 0;
251 			break;
252 		case 1:
253 			if (*str == '1') {
254 				ret = 1;
255 			} else if (*str == '0') {
256 				ret = 0;
257 			} else {
258 				ret = -1;
259 			}
260 			break;
261 		case 2:
262 			if (strncasecmp(str, "on", 2) == 0) {
263 				ret = 1;
264 			} else if (strncasecmp(str, "no", 2) == 0) {
265 				ret = 0;
266 			} else {
267 				ret = -1;
268 			}
269 			break;
270 		case 3:
271 			if (strncasecmp(str, "yes", 3) == 0) {
272 				ret = 1;
273 			} else if (strncasecmp(str, "off", 3) == 0) {
274 				ret = 0;
275 			} else {
276 				ret = -1;
277 			}
278 			break;
279 		case 4:
280 			if (strncasecmp(str, "true", 4) == 0) {
281 				ret = 1;
282 			} else {
283 				ret = -1;
284 			}
285 			break;
286 		case 5:
287 			if (strncasecmp(str, "false", 5) == 0) {
288 				ret = 0;
289 			} else {
290 				ret = -1;
291 			}
292 			break;
293 		default:
294 			ret = -1;
295 	}
296 
297 	if (ret == -1) {
298 		RETURN_VALIDATION_FAILED
299 	} else {
300 		zval_dtor(value);
301 		ZVAL_BOOL(value, ret);
302 	}
303 }
304 /* }}} */
305 
php_filter_float(PHP_INPUT_FILTER_PARAM_DECL)306 void php_filter_float(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
307 {
308 	int len;
309 	char *str, *end;
310 	char *num, *p;
311 
312 	zval **option_val;
313 	char *decimal;
314 	int decimal_set, decimal_len;
315 	char dec_sep = '.';
316 	char tsd_sep[3] = "',.";
317 
318 	long lval;
319 	double dval;
320 
321 	int first, n;
322 
323 	len = Z_STRLEN_P(value);
324 	str = Z_STRVAL_P(value);
325 
326 	PHP_FILTER_TRIM_DEFAULT(str, len);
327 	end = str + len;
328 
329 	FETCH_STRING_OPTION(decimal, "decimal");
330 
331 	if (decimal_set) {
332 		if (decimal_len != 1) {
333 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "decimal separator must be one char");
334 			RETURN_VALIDATION_FAILED
335 		} else {
336 			dec_sep = *decimal;
337 		}
338 	}
339 
340 	num = p = emalloc(len+1);
341 	if (str < end && (*str == '+' || *str == '-')) {
342 		*p++ = *str++;
343 	}
344 	first = 1;
345 	while (1) {
346 		n = 0;
347 		while (str < end && *str >= '0' && *str <= '9') {
348 			++n;
349 			*p++ = *str++;
350 		}
351 		if (str == end || *str == dec_sep || *str == 'e' || *str == 'E') {
352 			if (!first && n != 3) {
353 				goto error;
354 			}
355 			if (*str == dec_sep) {
356 				*p++ = '.';
357 				str++;
358 				while (str < end && *str >= '0' && *str <= '9') {
359 					*p++ = *str++;
360 				}
361 			}
362 			if (*str == 'e' || *str == 'E') {
363 				*p++ = *str++;
364 				if (str < end && (*str == '+' || *str == '-')) {
365 					*p++ = *str++;
366 				}
367 				while (str < end && *str >= '0' && *str <= '9') {
368 					*p++ = *str++;
369 				}
370 			}
371 			break;
372 		}
373 		if ((flags & FILTER_FLAG_ALLOW_THOUSAND) && (*str == tsd_sep[0] || *str == tsd_sep[1] || *str == tsd_sep[2])) {
374 			if (first?(n < 1 || n > 3):(n != 3)) {
375 				goto error;
376 			}
377 			first = 0;
378 			str++;
379 		} else {
380 			goto error;
381 		}
382 	}
383 	if (str != end) {
384 		goto error;
385 	}
386 	*p = 0;
387 
388 	switch (is_numeric_string(num, p - num, &lval, &dval, 0)) {
389 		case IS_LONG:
390 			zval_dtor(value);
391 			Z_TYPE_P(value) = IS_DOUBLE;
392 			Z_DVAL_P(value) = lval;
393 			break;
394 		case IS_DOUBLE:
395 			if ((!dval && p - num > 1 && strpbrk(num, "123456789")) || !zend_finite(dval)) {
396 				goto error;
397 			}
398 			zval_dtor(value);
399 			Z_TYPE_P(value) = IS_DOUBLE;
400 			Z_DVAL_P(value) = dval;
401 			break;
402 		default:
403 error:
404 			efree(num);
405 			RETURN_VALIDATION_FAILED
406 	}
407 	efree(num);
408 }
409 /* }}} */
410 
php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL)411 void php_filter_validate_regexp(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
412 {
413 	zval **option_val;
414 	char  *regexp;
415 	int regexp_len;
416 	long   option_flags;
417 	int    regexp_set, option_flags_set;
418 
419 	pcre       *re = NULL;
420 	pcre_extra *pcre_extra = NULL;
421 	int preg_options = 0;
422 
423 	int         ovector[3];
424 	int         matches;
425 
426 	/* Parse options */
427 	FETCH_STRING_OPTION(regexp, "regexp");
428 	FETCH_LONG_OPTION(option_flags, "flags");
429 
430 	if (!regexp_set) {
431 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "'regexp' option missing");
432 		RETURN_VALIDATION_FAILED
433 	}
434 
435 	re = pcre_get_compiled_regex(regexp, &pcre_extra, &preg_options TSRMLS_CC);
436 	if (!re) {
437 		RETURN_VALIDATION_FAILED
438 	}
439 	matches = pcre_exec(re, NULL, Z_STRVAL_P(value), Z_STRLEN_P(value), 0, 0, ovector, 3);
440 
441 	/* 0 means that the vector is too small to hold all the captured substring offsets */
442 	if (matches < 0) {
443 		RETURN_VALIDATION_FAILED
444 	}
445 }
446 /* }}} */
447 
php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL)448 void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
449 {
450 	php_url *url;
451 	int old_len = Z_STRLEN_P(value);
452 
453 	php_filter_url(value, flags, option_array, charset TSRMLS_CC);
454 
455 	if (Z_TYPE_P(value) != IS_STRING || old_len != Z_STRLEN_P(value)) {
456 		RETURN_VALIDATION_FAILED
457 	}
458 
459 	/* Use parse_url - if it returns false, we return NULL */
460 	url = php_url_parse_ex(Z_STRVAL_P(value), Z_STRLEN_P(value));
461 
462 	if (url == NULL) {
463 		RETURN_VALIDATION_FAILED
464 	}
465 
466 	if (url->scheme != NULL && (!strcasecmp(url->scheme, "http") || !strcasecmp(url->scheme, "https"))) {
467 		char *e, *s;
468 
469 		if (url->host == NULL) {
470 			goto bad_url;
471 		}
472 
473 		e = url->host + strlen(url->host);
474 		s = url->host;
475 
476 		/* First char of hostname must be alphanumeric */
477 		if(!isalnum((int)*(unsigned char *)s)) {
478 			goto bad_url;
479 		}
480 
481 		while (s < e) {
482 			if (!isalnum((int)*(unsigned char *)s) && *s != '-' && *s != '.') {
483 				goto bad_url;
484 			}
485 			s++;
486 		}
487 	}
488 
489 	if (
490 		url->scheme == NULL ||
491 		/* some schemas allow the host to be empty */
492 		(url->host == NULL && (strcmp(url->scheme, "mailto") && strcmp(url->scheme, "news") && strcmp(url->scheme, "file"))) ||
493 		((flags & FILTER_FLAG_PATH_REQUIRED) && url->path == NULL) || ((flags & FILTER_FLAG_QUERY_REQUIRED) && url->query == NULL)
494 	) {
495 bad_url:
496 		php_url_free(url);
497 		RETURN_VALIDATION_FAILED
498 	}
499 	php_url_free(url);
500 }
501 /* }}} */
502 
php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL)503 void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
504 {
505 	/*
506 	 * The regex below is based on a regex by Michael Rushton.
507 	 * However, it is not identical.  I changed it to only consider routeable
508 	 * addresses as valid.  Michael's regex considers a@b a valid address
509 	 * which conflicts with section 2.3.5 of RFC 5321 which states that:
510 	 *
511 	 *   Only resolvable, fully-qualified domain names (FQDNs) are permitted
512 	 *   when domain names are used in SMTP.  In other words, names that can
513 	 *   be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
514 	 *   in Section 5) are permitted, as are CNAME RRs whose targets can be
515 	 *   resolved, in turn, to MX or address RRs.  Local nicknames or
516 	 *   unqualified names MUST NOT be used.
517 	 *
518 	 * This regex does not handle comments and folding whitespace.  While
519 	 * this is technically valid in an email address, these parts aren't
520 	 * actually part of the address itself.
521 	 *
522 	 * Michael's regex carries this copyright:
523 	 *
524 	 * Copyright © Michael Rushton 2009-10
525 	 * http://squiloople.com/
526 	 * Feel free to use and redistribute this code. But please keep this copyright notice.
527 	 *
528 	 */
529 	const char regexp[] = "/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD";
530 
531 	pcre       *re = NULL;
532 	pcre_extra *pcre_extra = NULL;
533 	int preg_options = 0;
534 	int         ovector[150]; /* Needs to be a multiple of 3 */
535 	int         matches;
536 
537 
538 	/* The maximum length of an e-mail address is 320 octets, per RFC 2821. */
539 	if (Z_STRLEN_P(value) > 320) {
540 		RETURN_VALIDATION_FAILED
541 	}
542 
543 	re = pcre_get_compiled_regex((char *)regexp, &pcre_extra, &preg_options TSRMLS_CC);
544 	if (!re) {
545 		RETURN_VALIDATION_FAILED
546 	}
547 	matches = pcre_exec(re, NULL, Z_STRVAL_P(value), Z_STRLEN_P(value), 0, 0, ovector, 3);
548 
549 	/* 0 means that the vector is too small to hold all the captured substring offsets */
550 	if (matches < 0) {
551 		RETURN_VALIDATION_FAILED
552 	}
553 
554 }
555 /* }}} */
556 
_php_filter_validate_ipv4(char * str,int str_len,int * ip)557 static int _php_filter_validate_ipv4(char *str, int str_len, int *ip) /* {{{ */
558 {
559 	const char *end = str + str_len;
560 	int num, m;
561 	int n = 0;
562 
563 	while (str < end) {
564 		int leading_zero;
565 		if (*str < '0' || *str > '9') {
566 			return 0;
567 		}
568 		leading_zero = (*str == '0');
569 		m = 1;
570 		num = ((*(str++)) - '0');
571 		while (str < end && (*str >= '0' && *str <= '9')) {
572 			num = num * 10 + ((*(str++)) - '0');
573 			if (num > 255 || ++m > 3) {
574 				return 0;
575 			}
576 		}
577 		/* don't allow a leading 0; that introduces octal numbers,
578 		 * which we don't support */
579 		if (leading_zero && (num != 0 || m > 1))
580 			return 0;
581 		ip[n++] = num;
582 		if (n == 4) {
583 			return str == end;
584 		} else if (str >= end || *(str++) != '.') {
585 			return 0;
586 		}
587 	}
588 	return 0;
589 }
590 /* }}} */
591 
_php_filter_validate_ipv6(char * str,int str_len TSRMLS_DC)592 static int _php_filter_validate_ipv6(char *str, int str_len TSRMLS_DC) /* {{{ */
593 {
594 	int compressed = 0;
595 	int blocks = 0;
596 	int n;
597 	char *ipv4;
598 	char *end;
599 	int ip4elm[4];
600 	char *s = str;
601 
602 	if (!memchr(str, ':', str_len)) {
603 		return 0;
604 	}
605 
606 	/* check for bundled IPv4 */
607 	ipv4 = memchr(str, '.', str_len);
608 	if (ipv4) {
609  		while (ipv4 > str && *(ipv4-1) != ':') {
610 			ipv4--;
611 		}
612 
613 		if (!_php_filter_validate_ipv4(ipv4, (str_len - (ipv4 - str)), ip4elm)) {
614 			return 0;
615 		}
616 
617 		str_len = ipv4 - str; /* length excluding ipv4 */
618 		if (str_len < 2) {
619 			return 0;
620 		}
621 
622 		if (ipv4[-2] != ':') {
623 			/* don't include : before ipv4 unless it's a :: */
624 			str_len--;
625 		}
626 
627 		blocks = 2;
628 	}
629 
630 	end = str + str_len;
631 
632 	while (str < end) {
633 		if (*str == ':') {
634 			if (++str >= end) {
635 				/* cannot end in : without previous : */
636 				return 0;
637 			}
638 			if (*str == ':') {
639 				if (compressed) {
640 					return 0;
641 				}
642 				blocks++; /* :: means 1 or more 16-bit 0 blocks */
643 				compressed = 1;
644 
645 				if (++str == end) {
646 					return (blocks <= 8);
647 				}
648 			} else if ((str - 1) == s) {
649 				/* dont allow leading : without another : following */
650 				return 0;
651 			}
652 		}
653 		n = 0;
654 		while ((str < end) &&
655 		       ((*str >= '0' && *str <= '9') ||
656 		        (*str >= 'a' && *str <= 'f') ||
657 		        (*str >= 'A' && *str <= 'F'))) {
658 			n++;
659 			str++;
660 		}
661 		if (n < 1 || n > 4) {
662 			return 0;
663 		}
664 		if (++blocks > 8)
665 			return 0;
666 	}
667 	return ((compressed && blocks <= 8) || blocks == 8);
668 }
669 /* }}} */
670 
php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL)671 void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
672 {
673 	/* validates an ipv4 or ipv6 IP, based on the flag (4, 6, or both) add a
674 	 * flag to throw out reserved ranges; multicast ranges... etc. If both
675 	 * allow_ipv4 and allow_ipv6 flags flag are used, then the first dot or
676 	 * colon determine the format */
677 
678 	int            ip[4];
679 	int            mode;
680 
681 	if (memchr(Z_STRVAL_P(value), ':', Z_STRLEN_P(value))) {
682 		mode = FORMAT_IPV6;
683 	} else if (memchr(Z_STRVAL_P(value), '.', Z_STRLEN_P(value))) {
684 		mode = FORMAT_IPV4;
685 	} else {
686 		RETURN_VALIDATION_FAILED
687 	}
688 
689 	if ((flags & FILTER_FLAG_IPV4) && (flags & FILTER_FLAG_IPV6)) {
690 		/* Both formats are cool */
691 	} else if ((flags & FILTER_FLAG_IPV4) && mode == FORMAT_IPV6) {
692 		RETURN_VALIDATION_FAILED
693 	} else if ((flags & FILTER_FLAG_IPV6) && mode == FORMAT_IPV4) {
694 		RETURN_VALIDATION_FAILED
695 	}
696 
697 	switch (mode) {
698 		case FORMAT_IPV4:
699 			if (!_php_filter_validate_ipv4(Z_STRVAL_P(value), Z_STRLEN_P(value), ip)) {
700 				RETURN_VALIDATION_FAILED
701 			}
702 
703 			/* Check flags */
704 			if (flags & FILTER_FLAG_NO_PRIV_RANGE) {
705 				if (
706 					(ip[0] == 10) ||
707 					(ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) ||
708 					(ip[0] == 192 && ip[1] == 168)
709 				) {
710 					RETURN_VALIDATION_FAILED
711 				}
712 			}
713 
714 			if (flags & FILTER_FLAG_NO_RES_RANGE) {
715 				if (
716 					(ip[0] == 0) ||
717 					(ip[0] >= 240) ||
718 					(ip[0] == 127) ||
719 					(ip[0] == 169 && ip[1] == 254)
720 				) {
721 					RETURN_VALIDATION_FAILED
722 				}
723 			}
724 			break;
725 
726 		case FORMAT_IPV6:
727 			{
728 				int res = 0;
729 				res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value) TSRMLS_CC);
730 				if (res < 1) {
731 					RETURN_VALIDATION_FAILED
732 				}
733 				/* Check flags */
734 				if (flags & FILTER_FLAG_NO_PRIV_RANGE) {
735 					if (Z_STRLEN_P(value) >=2 && (!strncasecmp("FC", Z_STRVAL_P(value), 2) || !strncasecmp("FD", Z_STRVAL_P(value), 2))) {
736 						RETURN_VALIDATION_FAILED
737 					}
738 				}
739 				if (flags & FILTER_FLAG_NO_RES_RANGE) {
740 					switch (Z_STRLEN_P(value)) {
741 						case 1: case 0:
742 							break;
743 						case 2:
744 							if (!strcmp("::", Z_STRVAL_P(value))) {
745 								RETURN_VALIDATION_FAILED
746 							}
747 							break;
748 						case 3:
749 							if (!strcmp("::1", Z_STRVAL_P(value)) || !strcmp("5f:", Z_STRVAL_P(value))) {
750 								RETURN_VALIDATION_FAILED
751 							}
752 							break;
753 						default:
754 							if (Z_STRLEN_P(value) >= 5) {
755 								if (
756 									!strncasecmp("fe8", Z_STRVAL_P(value), 3) ||
757 									!strncasecmp("fe9", Z_STRVAL_P(value), 3) ||
758 									!strncasecmp("fea", Z_STRVAL_P(value), 3) ||
759 									!strncasecmp("feb", Z_STRVAL_P(value), 3)
760 								) {
761 									RETURN_VALIDATION_FAILED
762 								}
763 							}
764 							if (
765 								(Z_STRLEN_P(value) >= 9 &&  !strncasecmp("2001:0db8", Z_STRVAL_P(value), 9)) ||
766 								(Z_STRLEN_P(value) >= 2 &&  !strncasecmp("5f", Z_STRVAL_P(value), 2)) ||
767 								(Z_STRLEN_P(value) >= 4 &&  !strncasecmp("3ff3", Z_STRVAL_P(value), 4)) ||
768 								(Z_STRLEN_P(value) >= 8 &&  !strncasecmp("2001:001", Z_STRVAL_P(value), 8))
769 							) {
770 								RETURN_VALIDATION_FAILED
771 							}
772 					}
773 				}
774 			}
775 			break;
776 	}
777 }
778 /* }}} */
779 
php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL)780 void php_filter_validate_mac(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
781 {
782 	char *input = Z_STRVAL_P(value);
783 	int input_len = Z_STRLEN_P(value);
784 	int tokens, length, i, offset, exp_separator_set, exp_separator_len;
785 	char separator;
786 	char *exp_separator;
787 	long ret = 0;
788 	zval **option_val;
789 
790 	FETCH_STRING_OPTION(exp_separator, "separator");
791 
792 	if (exp_separator_set && exp_separator_len != 1) {
793 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Separator must be exactly one character long");
794 		RETURN_VALIDATION_FAILED;
795 	}
796 
797 	if (14 == input_len) {
798 		/* EUI-64 format: Four hexadecimal digits separated by dots. Less
799 		 * commonly used but valid nonetheless.
800 		 */
801 		tokens = 3;
802 		length = 4;
803 		separator = '.';
804 	} else if (17 == input_len && input[2] == '-') {
805 		/* IEEE 802 format: Six hexadecimal digits separated by hyphens. */
806 		tokens = 6;
807 		length = 2;
808 		separator = '-';
809 	} else if (17 == input_len && input[2] == ':') {
810 		/* IEEE 802 format: Six hexadecimal digits separated by colons. */
811 		tokens = 6;
812 		length = 2;
813 		separator = ':';
814 	} else {
815 		RETURN_VALIDATION_FAILED;
816 	}
817 
818 	if (exp_separator_set && separator != exp_separator[0]) {
819 		RETURN_VALIDATION_FAILED;
820 	}
821 
822 	/* Essentially what we now have is a set of tokens each consisting of
823 	 * a hexadecimal number followed by a separator character. (With the
824 	 * exception of the last token which does not have the separator.)
825 	 */
826 	for (i = 0; i < tokens; i++) {
827 		offset = i * (length + 1);
828 
829 		if (i < tokens - 1 && input[offset + length] != separator) {
830 			/* The current token did not end with e.g. a "." */
831 			RETURN_VALIDATION_FAILED
832 		}
833 		if (php_filter_parse_hex(input + offset, length, &ret TSRMLS_CC) < 0) {
834 			/* The current token is no valid hexadecimal digit */
835 			RETURN_VALIDATION_FAILED
836 		}
837 	}
838 }
839 /* }}} */
840 
841 /*
842  * Local variables:
843  * tab-width: 4
844  * c-basic-offset: 4
845  * End:
846  * vim600: noet sw=4 ts=4 fdm=marker
847  * vim<600: noet sw=4 ts=4
848  */
849