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